This is the sixth day of my participation in the August More text Challenge. For details, see:August is more challenging
Thread-safe collections
WangScaler: an author who works with his heart.
Statement: talent and learning shallow, if there is a mistake, kindly correct.
An unsafe set
For everyday coding, do we often use collections like ArrayList, HashSet, and HashMap? Did you know that these collections are not safe in multithreading, for example.
package com.wangscaler.securecollection;
import java.util.*;
/ * * *@author WangScaler
* @date2021/8/5 arieh * /
public class NotSafeCollection {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
int number = 3;
Random random = new Random();
for (int i = 0; i < number; i++) {
new Thread(() -> {
int randomNumber = random.nextInt(10) % 11;
list.add(randomNumber);
}, String.valueOf(i)).start();
}
while (Thread.activeCount() > 2) { Thread.yield(); } list.forEach(System.out::println); }}Copy the code
You might want to use three threads to populate the ArrayList, but the result might be null; null; 3 May also be null; 2; 3, of course, it is possible to achieve your desired effect 1,2,3.
Why does this happen? Let’s open up the source code
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
Copy the code
But three lines of code are executed with bytecode as follows
0 aload_0
1 aload_0
2 getfield #284 <java/util/ArrayList.size : I>
5 iconst_1
6 iadd
7 invokespecial #309 <java/util/ArrayList.ensureCapacityInternal : (I)V>
10 aload_0
11 getfield #287 <java/util/ArrayList.elementData : [Ljava/lang/Object;>
14 aload_0
15 dup
16 getfield #284 <java/util/ArrayList.size : I>
19 dup_x1
20 iconst_1
21 iadd
22 putfield #284 <java/util/ArrayList.size : I>
25 aload_1
26 aastore
27 iconst_1
28 ireturn
Copy the code
Threads are executed alternately while the program is executing. Our example above has three threads, thread 1, thread 2, and thread 3.
Putfield #284 < Java /util/ arrayList. size: I> is written before aastore, that is, size is written back to main memory before array is written back. It is possible that thread 2 will execute the array before thread 1 has written it in, and the size will be incrementing by one, so thread 2 will save the new value to the array, and null will appear. 2; In the case of 3.
A HashMap is also thread-unsafe, as is a HashSet, because the underlying HashSet is a HashMap
public HashSet(a) {
map = new HashMap<>();
}
Copy the code
So what’s the difference between a HashMap and a HashSet? We know that a HashMap is in the form of a key-value pair, while a HashSet has a fixed value.
private static final Object PRESENT = new Object();
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
Copy the code
In addition to being thread unsafe, a HashMap can also cause dead chains when it is expanded before JDK8.
How to solve it?
1. Legacy security collections
- The Vector used in the ArrayList
-
HashTable for HashMap
For example: Set the above List
List = new ArrayList<>(); List
List = new Vector<>(); Can. Why does this solve the problem? Open source
public synchronized boolean add(E e) { modCount++; ensureCapacityHelper(elementCount + 1); elementData[elementCount++] = e; return true; } Copy the code
Is a synchronous method that solves the problem with mutex, but has been deprecated because it is extremely inefficient in multithreading. HashTable is also deprecated due to synchronization issues.
2, the Collections
Make insecure Collections secure through the collection decoration.
- SynchronizedList for ArrayList
- SynchronizedMap to HashMap
- SynchronizedSet for HashSet
Wrapping a thread-safe class over an already unsafe set achieves the desired effect. For example: Set the above List
List = new ArrayList<>(); Modified to List < Integer > List = Collections. SynchronizedList (new ArrayList < > ()); Can be solved. How does that work? Again, look at the source code
SynchronizedList(List<E> list) {
super(list);
this.list = list;
}
public E get(int index) {
synchronized (mutex) {return list.get(index);}
}
public E set(int index, E element) {
synchronized (mutex) {return list.set(index, element);}
}
public void add(int index, E element) {
synchronized (mutex) {list.add(index, element);}
}
public E remove(int index) {
synchronized (mutex) {return list.remove(index);}
}
Copy the code
Synchronized = synchronized = synchronized = synchronized = synchronized = synchronized
3, JUC
-
Bloacking
- ArrayBlockingQueue
- LinkedBlockingQueue
- LinkedBlockingDeque
- .
-
CopyOnWrite
- CopyOnWriteArrayList corresponding ArrayList
- CopyOnWriteArraySet is used for HashSet, and the underlying layer is still CopyOnWriteArrayList
-
Concurrent (recommended, weak consistency.)
-
ConcurrentHashMap used HashMap
Only one operation can be guaranteed to be atomic, such as checking whether the key is present first (get) and not adding (put). Two operations cannot be guaranteed to be atomic, so you should use computeIfAbsent().
-
ConcurrentSkipListMap
-
ConcurrentSkipListSet
-
.
-
For example: Set the above List
List = new ArrayList<>(); Change to List
List = new CopyOnWriteArrayList<>();
Good for scenarios where you read more than you write. Look at the source
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally{ lock.unlock(); }}Copy the code
When writing, lock the data, make a copy, write the new array to the new array, and finally write the new array back to the original resource, thus ensuring the atomicity of the data. This process only adds the lock to the method, does not affect the read operation.
conclusion
In multi-threaded synchronization method greatly affects the efficiency of work, so ConcurrentHashMap through volatile combined with spin lock, widely popular, also is one of the interviewers often asked questions, it is worth everyone to read the source code.