Java Concurrent Programming - CopyOnWriteArrayList

Posted by Fira on Thu, 23 Dec 2021 21:17:15 +0100

1 Overview

The JUC concurrency package only provides one concurrency list, CopyOnWriteList, which is thread safe (there is an exclusive lock ReetrantLock inside) and adopts the write time replication strategy. The overall class diagram structure is shown in the following figure.

The following describes this class in detail from the construction method, addition, deletion, modification and iterator traversal.

2 construction method

This class has three construction methods: one is a parameterless construction, one is a parameterless construction that passes in a collection, and the other is a parameterless construction that passes in a generic array

public CopyOnWriteArrayList() //The parameterless constructor internally creates an Object array with a size of 0
public CopyOnWriteArrayList(Collection<? extends E> c) //Pass in a collection and assign the elements in the collection to this list
public CopyOnWriteArrayList(E[] toCopyIn) //A copy of toCopyIn is passed in when a list is created with its internal elements

3 add element

public boolean add(E e) {
            //Get internal exclusive lock
            final ReentrantLock lock = this.lock;
            lock.lock();
            try {
                //Gets the array of current internal maintenance
                Object[] elements = getArray();
                int len = elements.length;
                //Create a new array and copy the elements of the original array to the new array, and the size of the new array is the size of the original array plus 1
                Object[] newElements = Arrays.copyOf(elements, len + 1);
                //Add the element to be added to the new array
                newElements[len] = e;
                //Update Object [] of current list
                setArray(newElements);
                return true;
            } finally {
                lock.unlock();//Release lock resource
            }
        }

There are other methods for adding elements in this class, such as add (int index, e element), addifabsent (e e e), etc. its principle is basically the same as the above add (e e e) method

4 get the element at the specified location

It mainly refers to the get() method of this class. At this time, three method calls are designed

        //Gets the location of the element at the specified location
        public E get(int index) {
            return get(getArray(), index);
        }
        //Get internal array
        final Object[] getArray() {
            return array;
        }
        //After passing in an array and index, this get method attempts to get the element at the index position of the passed in array
        private E get(Object[] a, int index) {
            return (E) a[index];
        }

You can see that the process of obtaining the element at the specified location is not locked, so the process is not thread safe, so there is the so-called weak consistency problem, as shown in the following figure

Thread a calls get(0) to obtain the elements at the position of List index 0. Looking at the source code, we can see that there are three steps in total. When the second step is completed and the last step get(Object[] a, int index) is executed, thread B performs the add(0, 4) operation on the List, that is, at this time, the elements at the position of array index 0 in the List change. Although the array is volatile, thread a can know immediately, However, when the thread executes the last step of the query, the passed in Object [] array a (that is, tempArray in the figure) is still the original array. At this time, the original array is used for query, and the returned value is 1 instead of 4. When the query is completed, there are no other references to tempArray, which becomes garbage waiting for GC collection.

5 modify the specified element

Using the set(int index, E element) method, you can see that the copy on write strategy is also adopted

public E set(int index, E element) {
            //Get exclusive lock
            final ReentrantLock lock = this.lock;
            lock.lock();
            try {
                //Get the current array
                Object[] elements = getArray();
                //Gets the element at the index position in the current array
                E oldValue = get(elements, index);
                //When the original element is inconsistent with the element to be reset, enter the modification logic
                if (oldValue != element) {
                    //After copying a copy of the current array, modify the element at the corresponding position to element, and then set it to the current array
                    int len = elements.length;
                    Object[] newElements = Arrays.copyOf(elements, len);
                    newElements[index] = element;
                    setArray(newElements);
                } else {
                    // Not quite a no-op; ensures volatile write semantics
                    setArray(elements);
                }
                //Return old element
                return oldValue;
            } finally {
                lock.unlock();
            }
        }

6 delete element

Here we mainly look at the remove(int index) method. The principles of other methods for deleting elements are basically the same

public E remove(int index) {
            //Get exclusive lock
            final ReentrantLock lock = this.lock;
            lock.lock();
            try {
                //Get the current array
                Object[] elements = getArray();
                int len = elements.length;
                E oldValue = get(elements, index);//Gets the value at the index location to delete
                //The following operation is to copy the elements before and after the index position into the new array in two steps
                int numMoved = len - index - 1;
                if (numMoved == 0)
                    setArray(Arrays.copyOf(elements, len - 1));
                else {
                    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();
            }
        }

7 iterator traversal

//This is the method to get iterators in the List. A cowiterator is created according to the current array (this is an internal class in CopyOnWriteArrayList)
public Iterator<E> iterator() {
        return new COWIterator<E>(getArray(), 0);
    }

The traversal operation after obtaining the iterator is the same as that of an ordinary collection, which will not be introduced here. It should be noted that the iterator also has the problem of weak consistency, because we can see that the obtaining iterator will pass in the current array. If other threads add, delete and modify the List before the current thread uses the iterator for traversal, Then the result of the iteration is still the original array for the same reason as when the get() method is mentioned above.  

Topics: Java Back-end Concurrent Programming