Collection thread safety issues
JDK Version:9
First, let's talk about what set thread safety is: when multiple threads add and query the same set, an abnormal error occurs.
Recurrence example:
package com.JUC; import java.util.ArrayList; import java.util.List; import java.util.UUID; public class ListSecutity04 { public static void main(String[] args) { List<String> list = new ArrayList<>(); for (int i = 0; i < 100; i++) { new Thread(()->{ list.add(UUID.randomUUID().toString().substring(0,8)); System.out.println(list); },String.valueOf(i)).start(); } } }
design sketch:
You can see concurrent modificationexception reported; The problem with this error is that the add method in ArrayList is not locked.
View its source code:
public boolean add(E e) { modCount++; add(e, elementData, size); return true; } //---------------------------------------- public void add(int index, E element) { rangeCheckForAdd(index); modCount++; final int s; Object[] elementData; if ((s = size) == (elementData = this.elementData).length) elementData = grow(); System.arraycopy(elementData, index, elementData, index + 1, s - index); elementData[index] = element; size = s + 1; } //---------------------------------------- private void add(E e, Object[] elementData, int s) { if (s == elementData.length) elementData = grow(); elementData[s] = e; size = s + 1; }
Solution -: Vector and conflicts
See the name and meaning. The two methods of title are relatively old methods. Vector is used because it is modified by the synchronized keyword added in its add method
public synchronized boolean add(E e) { modCount++; add(e, elementData, elementCount); return true; }
Another method is to return the synchronized (thread safe) collection supported by the specified collection in the tool class synchronizedcollection (collection < T > C) in the collection.
Repeat the test and the code passes:
package com.JUC; import java.util.*; public class ListSecutity04 { public static void main(String[] args) { // List<String> list = new ArrayList<>(); // List<String> list = new Vector<>(); List<String> list = Collections.synchronizedList(new ArrayList()); for (int i = 0; i < 100; i++) { new Thread(()->{ list.add(UUID.randomUUID().toString().substring(0,8)); System.out.println(list); },String.valueOf(i)).start(); } } }
However, we choose CopyOnWriteArrayList;
Solution 2: CopyOnWriteArrayList
Copy on write technology:
List<String> list = new CopyOnWriteArrayList<>();
Its idea:
In the case of multithreading, when writing to the collection, the system first copies the original content (A) as (B), the original content (A) can be read concurrently, and the copied content (B) is written to the new content. When the content writing is completed, A and B realize coverage or merging.
The definition of array is volatile, so that each thread can observe the change of array in real time.
private transient volatile Object[] array; final void setArray(Object[] a) { array = a; }
add() method source code: lock the lock object
final transient Object lock = new Object();
public boolean add(E e) { synchronized (lock) { Object[] elements = getArray(); //Get original content int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len + 1); //Array copy newElements[len] = e; //Add new content setArray(newElements); //Overwrite original array return true; } }
CopyOnWriteArrayList is characterized by the separation of reading and writing, and the final consistency. Better performance than synchronized pessimistic locks. The disadvantage is that replication takes up memory, and OOM may occur.
Similarly, thread unsafe collections include HashMap and HashSet. Their solutions are similar. They all correspond in JUC,
We can also write the code and enter the source code for a simple analysis:
HashSet--CopyOnWriteArraySet
First, check the source code of the add method in the HashSet: (thread unsafe)
public boolean add(E e) { return map.put(e, PRESENT)==null; } //---------In map public V put(K key, V value) { return putVal(hash(key), key, value, false, true); }
It can be seen that the put method in the map set is used at the bottom of the set set. When you enter the method, there is no code modification related to synchronization.
It can also be seen that the set is unordered and not repeated. The E passed in the set is finally passed into the map as a key.
Then check the source code of add in CopyOnWriteArraySet: (thread safety)
public boolean add(E e) { return al.addIfAbsent(e); //CopyOnWriteArrayList<E> al = new CopyOnWriteArrayList<E>() } //-------------addIfAbsent--------------- public boolean addIfAbsent(E e) { Object[] snapshot = getArray(); return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false : addIfAbsent(e, snapshot); } //---------------------addIfAbsent------------------------ private boolean addIfAbsent(E e, Object[] snapshot) { synchronized (lock) { Object[] current = getArray(); int len = current.length; if (snapshot != current) { // Optimize for lost race to another addXXX operation int common = Math.min(snapshot.length, len); for (int i = 0; i < common; i++) if (current[i] != snapshot[i] && Objects.equals(e, current[i])) return false; if (indexOf(e, current, common, len) >= 0) return false; } Object[] newElements = Arrays.copyOf(current, len + 1); newElements[len] = e; setArray(newElements); return true; }
As can be seen from the source code, set uses CopyOnWriteArrayList, and the finally added data is synchronized in the addIfAbsent method.
HashMap--ConcurrentHashMap
The solution of HashMap is ConcurrentHashMap, which is also modified by synchronized in its source code. The specific addition process is not understood in the code, so it is not shown for the time being.
Welcome friends to share this part of the high-quality blog.