Introduce
In the comments of ArrayList, JDK tells us that when ArrayList (JDK 1.2) is used as a thread shared variable, it is unsafe for threads. We are recommended to lock or use connection. Synchronized List (), while Vector (JDK 1.0) is not recommended, because the premise of Vector to ensure thread safety is to add synchronized heavy lock, which seriously affects performance, So it provides a new thread safe List - copyonwritearraylist (JDK 1.5)
Features
1. Thread safety without lock
2. Ensure thread safety by using read-write lock + array copy + volatile
3. During the write operation, the array will be copied out and operated on the new array. After the operation is successful, the array will be assigned back
4. There is no need to copy during read operation. The idea of separation of read and write is used. However, the underlying array is modified with volatile, which is visible to threads
5. Consistent with the ArrayList data structure, the bottom layer is array
General induction
CopyOnWriteArrayList operates on arrays in four steps:
1. Lock reading and writing lock
2. Copy a new array from the original array
3. Operate on the new array and assign the new array to the original array container
4. Release the lock (finally release to prevent memory leakage)
Source code analysis
Underlying array container
private transient volatile Object[] array; //volatile decoration visible to threads
Construction method
//The nonparametric construction will directly initialize an array and assign the array to the underlying array container public CopyOnWriteArrayList() { setArray(new Object[0]); } /* A parameterized construct needs to pass a collection of subtypes that can receive E types or E If the type is the same as CopyOnWriteArrayList, its Array assigned to the new CopyOnWriteArrayList collection If they are not the same, convert them to an array and then copy the array */ public CopyOnWriteArrayList(Collection<? extends E> c) { Object[] elements; if (c.getClass() == CopyOnWriteArrayList.class) elements = ((CopyOnWriteArrayList<?>)c).getArray(); else { 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); } setArray(elements); }
Tail added
public boolean add(E e) { final ReentrantLock lock = this.lock; lock.lock(); //Lock up try { Object[] elements = getArray(); //Get the original array int len = elements.length; //Copy new array new length + 1 Object[] newElements = Arrays.copyOf(elements, len + 1); //Add a new node at the end of the array newElements[len] = e; //New array overwrites the original array setArray(newElements); return true; } finally { //Release lock lock.unlock(); } }
Lock is used here to ensure that only one thread can add to the same array at the same time. In addition to lock, a new array will be created from the old array and the value will be copied instead of operating on the original array. There are new problems. Why copy the array instead of operating on the array?
As a result of:
volatile keyword modifies the original array. If you only modify the values of several nodes in the array, visibility will not be triggered. Therefore, you must modify the memory address of the array, that is to say, you need to reassign the array
The new array is copied, which has no effect on the old array. Only after the new array is copied, can it be accessed externally, which reduces the impact of the changes of the old array during the assignment process
New at index
public void add(int index, E element) { final ReentrantLock lock = this.lock; lock.lock();//Lock up try { Object[] elements = getArray(); int len = elements.length; //Judge the relationship between index and original array length if (index > len || index < 0) throw new IndexOutOfBoundsException("Index: "+index+ ", Size: "+len); //New array Object[] newElements; int numMoved = len - index; //Index = add node directly at the end of array if (numMoved == 0) newElements = Arrays.copyOf(elements, len + 1); else { newElements = new Object[len + 1]; //Array copy copy 0--index System.arraycopy(elements, 0, newElements, 0, index); //Array copy copy index+1 -- end System.arraycopy(elements, index, newElements, index + 1, numMoved); } //index is assigned to an empty node newElements[index] = element; //Array assignment setArray(newElements); } finally { //Release lock lock.unlock(); } }
Summary
1. Lock: ensure that the array can only be operated by one thread at the same time
2. Array copy: ensure the visibility of volatile triggered by array memory address modification
3.volatile: after the value is modified, other threads immediately perceive
Delete node at index
public E remove(int index) { final ReentrantLock lock = this.lock; lock.lock(); //Lock up try { Object[] elements = getArray(); int len = elements.length; E oldValue = get(elements, index); //Old value //If you copy 0--len-1 directly at the end of the array int numMoved = len - index - 1; if (numMoved == 0) setArray(Arrays.copyOf(elements, len - 1)); else { //If you do not copy the array twice, the first time 0--len-1, the second time index+1 -- tail Object[] newElements = new Object[len - 1]; System.arraycopy(elements, 0, newElements, 0, index); System.arraycopy(elements, index + 1, newElements, index, numMoved); setArray(newElements); } return oldValue; } finally { lock.unlock(); //Release lock } }
Summary
1. lock up
2. Segmented copy
3. release lock
Batch deletion
public boolean removeAll(Collection<?> c) { if (c == null) throw new NullPointerException(); final ReentrantLock lock = this.lock; lock.lock(); //Lock up try { Object[] elements = getArray(); int len = elements.length; if (len != 0) { int newlen = 0; Object[] temp = new Object[len];//Create a new array for (int i = 0; i < len; ++i) { Object element = elements[i]; //Place elements that do not contain the c set in the new array from 0 if (!c.contains(element)) temp[newlen++] = element; } if (newlen != len) { setArray(Arrays.copyOf(temp, newlen)); return true; } } return false; } finally { lock.unlock(); //Release lock } }
From the source code, we can see that CopyOnWriteArrayList does not delete elements one by one, but circularly judges the array, putting the data we don't need to delete into the temporary array, and finally wetting the array is the data we don't need to delete
If you use delete one by one, you will copy the array once every time you delete it, which consumes time and performance. It takes a long time to lock and has a large concurrency, resulting in a large number of requests waiting for locks, which also takes up a certain amount of memory
Query IndexOf
private static int indexOf(Object o Search object, Object[] elements Search target array, int index Starting position, int fence End position) { //Query element is null traverse put back if (o == null) { for (int i = index; i < fence; i++) if (elements[i] == null) return i; } else { //Not null traversal for (int i = index; i < fence; i++) if (o.equals(elements[i])) return i; } return -1; }
iteration
During the iteration, if the original value of the array is changed, the ConcurrentModificationException will not be thrown. The reason is that the read and write are separated. Each operation will generate a new array without affecting the old array
summary
When we use List when the thread is unsafe, we recommend copyonwritearraylist. Copyonwritearraylist implements the thread safety of List by cooperating with lock + array copy + volatile