CopyOnWriteArrayList import
Thread insecurity occurs when simulating the traditional ArrayList
public class Demo1 { public static void main(String[] args) { //List<String> list = new CopyOnWriteArrayList<>(); List<String> list = new ArrayList<>(); //Start 50 threads to add data to ArrayList for (int i = 1; i <= 50; i++) { new Thread(() -> { list.add(UUID.randomUUID().toString().substring(0, 5)); System.out.println(list); }, String.valueOf(i)).start(); } } }
The running results are as follows: due to the existence of the fail fast mechanism, the error of modcount modification exception is thrown (modcount is a variable in the source code of ArrayList, which is used to indicate the number of modifications, because ArrayList is not a collection class designed for concurrency)
How to solve this problem?
Method 1: the Vector set can be used. The Vector set is an ArrayList of thread safe version. Its methods are decorated with a layer of synchronized. The jvm built-in lock is adopted to ensure its atomicity, visibility and order in the case of concurrency. But it also brings performance problems, because once synchronized expands to heavyweight locks, there is a change in user state and mentality, and multithreaded context switching will bring overhead. Another problem is that the expansion of the Vector set is not as good as the ArrayList strategy
List<String> list = new Vector<>();
Method 2: use collections synchronizedList
List<String> list = Collections.synchronizedList(new ArrayList<>());
Method 3: adopt the concurrent container provided by JUC, CopyOnWriteArrayList
List<String> list = new CopyOnWriteArrayList<>();
Analysis of CopyOnWriteArrayList
Like ArrayList, its underlying data structure is also an array. In addition, transient does not allow it to be serialized, and volatile decoration is added to ensure its visibility and order under multithreading
Let's look at its constructor first
public CopyOnWriteArrayList() { //An array of size 0 is created by default setArray(new Object[0]); } final void setArray(Object[] a) { array = a; } public CopyOnWriteArrayList(Collection<? extends E> c) { Object[] elements; //If the current collection is of type CopyOnWriteArrayList, assign it directly if (c.getClass() == CopyOnWriteArrayList.class) elements = ((CopyOnWriteArrayList<?>)c).getArray(); else { //Otherwise, call toArra() to turn it into an array elements = c.toArray(); // c.toArray might (incorrectly) not return Object[] (see 6260652) if (elements.getClass() != Object[].class) elements = Arrays.copyOf(elements, elements.length, Object[].class); } //Set array setArray(elements); } public CopyOnWriteArrayList(E[] toCopyIn) { //Copy the passed in array elements to the current array setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class)); }
Let's take a look at several operations to read data. It can be seen that they are not locked, which is strange. How to ensure thread safety?
final Object[] getArray() { return array; } public int size() { return getArray().length; } public boolean isEmpty() { return size() == 0; } public int indexOf(E e, int index) { Object[] elements = getArray(); return indexOf(e, elements, index, elements.length); } public int lastIndexOf(Object o) { Object[] elements = getArray(); return lastIndexOf(o, elements, elements.length - 1); }
Let's take a look at the add function when it is modified
public boolean add(E e) { //Lock with ReentrantLock final ReentrantLock lock = this.lock; lock.lock(); try { //Call getArray() to get the original array Object[] elements = getArray(); int len = elements.length; //Copy the old array to get an array with length + 1 Object[] newElements = Arrays.copyOf(elements, len + 1); //Add elements and replace the original array with setArray() function newElements[len] = e; setArray(newElements); return true; } finally { lock.unlock(); } }
It can be seen that the modification operation is based on the fail safe mechanism. Like our String, we do not directly operate on the original object, but copy a copy to modify it. In addition, the modification operation here is locked with a Lock lock, so thread safety is guaranteed.
Let's take a look at the remove operation to see if it does so
public boolean remove(Object o) { Object[] snapshot = getArray(); int index = indexOf(o, snapshot, 0, snapshot.length); return (index < 0) ? false : remove(o, snapshot, index); } private boolean remove(Object o, Object[] snapshot, int index) { final ReentrantLock lock = this.lock; //Lock lock.lock(); try { Object[] current = getArray(); int len = current.length; if (snapshot != current) findIndex: { int prefix = Math.min(index, len); for (int i = 0; i < prefix; i++) { if (current[i] != snapshot[i] && eq(o, current[i])) { index = i; break findIndex; } } if (index >= len) return false; if (current[index] == o) break findIndex; index = indexOf(o, current, index, len); if (index < 0) return false; } //Copy an array Object[] newElements = new Object[len - 1]; System.arraycopy(current, 0, newElements, 0, index); System.arraycopy(current, index + 1, newElements, index, len - index - 1); //Replace original array setArray(newElements); return true; } finally { lock.unlock(); } }
It can be seen that the idea is the same. We compare it with ArrayList. It can be seen that its efficiency is much lower than ArrayList. After all, in a multi-threaded scenario, it needs to copy a copy based on the original array every time, which consumes memory and time. However, ArrayList is only expanded when its capacity is full. Therefore, in a non multi-threaded scenario, we'd better use ArrayList.
This also solves my previous question. Why do you still learn ArrayList? The CopyOnWriteArrayList of JUC version can do things that ArrayList can't do. It's also very fragrant for us to directly use CopyOnWriteArrayList.
Summary
CopyOnWriteArrayList is suitable for multi-threaded scenarios. It adopts the idea of separation of read and write. The read operation is not locked, the write operation is locked, and the write operation efficiency is low
CopyOnWriteArrayList is based on the fail safe mechanism. Each modification will copy a copy of the original basis and replace it after modification
CopyOnWriteArrayList is locked with ReentrantLock.