Let’s take a look at the in-depth analysis of the CopyOnWriteArrayList thread-safe collection from the source code.

Table of Contents

1 Introduction

2. A brief overview of the principle of CpoyOnWriteArrayList

3. CopyOnWriteArrayList source code analysis

3.1 Interpretation of property constructor

3.2 get method analysis

3.3 add method analysis

3.4 set method analysis

3.5 Remove method analysis

4. Summary


1. Foreword

Students who have used the ArrayList collection should generally know that ArrayList is a non-thread-safe collection; similarly, Java also provides us with a thread-safe List collection, which is the CopyOnWriteArrayList we are going to talk about in this article. Because other methods are usually used to ensure thread safety during the development process, it is not used as frequently as ArrayList.

2. A brief overview of the CpoyOnWriteArrayList principle

CopyOnWriteArrayListThe bottom layer uses locking to ensure thread safety, and the Lock lock is added instead of the Sychonized lock.

If there are two threads, a reading thread A and a writing thread B, and they want to add elements to the array at the same time, the reading thread A will read the CopyOnWriteArrayList collection in the current memory, and the writing thread B will add elements in the memory. CopyOnWriteArrayList makes a new copy of the collection object, and performs the add operation in the newly copied collection. After the add operation is completed, the new collection is assigned to the original old collection, and during this process, the writing thread B will acquire the only Lock lock, and the other The writing thread will block and wait to achieve separation of reading and writing. So if there is a third writing thread C that also wants to perform a write data operation, it needs to wait for the write thread B to release the Lock after the operation is completed and obtain the Lock before it can perform the write operation.

3. CopyOnWriteArrayList source code analysis

3.1 Interpretation of Attribute Constructor

Below are some of the attributes, get, set methods, and construction methods I pasted.

(1) You can see that inside CopyOnWriteArrayList it defines a Lock object;

(2) The bottom layer defines an array of objects named array;

(3) Parameterless construction It can be seen that calling the parameterless construction will set the length of the array array object to 0, and it will only go back to expand when storing elements;

public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    private static final long serialVersionUID = 8673264195747942595L;

    /** The lock protecting all mutators */
    final transient ReentrantLock lock = new ReentrantLock();

    /** The array, accessed only via getArray/setArray. */
    private transient volatile Object[] array;

    /**
     * Gets the array. Non-private so as to also be accessible
     * from CopyOnWriteArraySet class.
     */
    final Object[] getArray() {
        return array;
    }

    /**
     * Sets the array.
     */
    final void setArray(Object[] a) {
        array = a;
    }

    /**
     * Creates an empty list.
     */
    public CopyOnWriteArrayList() {
        setArray(new Object[0]);
    }
3.2 get method analysis

The following is the get method of CopyOnWriteArrayList to obtain elements. Here, neither the index nor the length of the array is judged, so it is very likely that an index out-of-bounds exception will occur;

The get method to obtain elements is very simple and there is no locking behavior.

public E get(int index) {
// Directly return the element at the index position of the object
        return get(getArray(), index);
    }
3.3 add method analysis
public void add(int index, E element) {
// Get the Lock lock
        final ReentrantLock lock = this.lock;
// Call method to lock
        lock.lock();
        try {
// Get the memory array object and assign it to elements
            Object[] elements = getArray();
//Define a variable len to get the length of the array
            int len = elements.length;
// Determine whether the parameter index of the method is out of bounds or legal
            if (index > len || index < 0)
                throw new IndexOutOfBoundsException("Index: " + index +
                                                    ", Size: " + len);
//Define a new array object newElements
            Object[] newElements;
//Define a variable numMoved to receive the length of the array - the value of index
            int numMoved = len - index;
// If numMoved is 0, it means that the newly added element should be placed at the end of the array
            if (numMoved == 0)
//Call the copyOf method to copy all the data in the original array to newElements.
// and add new elements to the end of the array
                newElements = Arrays.copyOf(elements, len + 1);
            else {
// If numMoved is not 0, it means that the element is to be added somewhere in the middle of the array
// First add the length of the new array + 1
                newElements = new Object[len + 1];
//Copy all the data between 0~index of the old array to the new array
                System.arraycopy(elements, 0, newElements, 0, index);
// Then copy all the last data of the index~ array to the new array
                System.arraycopy(elements, index, newElements, index + 1,
                                 numMoved);
            }
//Add the element element to be added to the index position of the new array
            newElements[index] = element;
//Assign the old array address value to the new array object
            setArray(newElements);
        } finally {
// After the operation is completed, finally release the lock
            lock.unlock();
        }
    }
3.4 set method analysis
public E set(int index, E element) {
// Get the Lock lock
        final ReentrantLock lock = this.lock;
// Call method to lock
        lock.lock();
        try {
// Get the memory array object and assign it to a new array object elements
            Object[] elements = getArray();
// Get the element at index
            E oldValue = get(elements, index);
// Determine whether oldValue is equal to the element to be inserted
            if (oldValue != element) {
// Get the length of the array
                int len = elements.length;
//Copy the original array data to the new array newElements
                Object[] newElements = Arrays.copyOf(elements, len);
// Place element at the index of the new array
                newElements[index] = element;
// Overwrite the original array with the new array
                setArray(newElements);
            } else {
                // Not quite a no-op; ensures volatile write semantics
// Enter else, indicating that the element to be set already exists in the array, and return the original array directly.
                setArray(elements);
            }
// Return the old element at position index
            return oldValue;
        } finally {
//The operation is completed, release the lock lock
            lock.unlock();
        }
    }
3.5 remove method analysis
public E remove(int index) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
// Get the length of the array
            int len = elements.length;
// Get the element at index
            E oldValue = get(elements, index);
// Define numMoved to calculate the number of elements to be moved
            int numMoved = len - index - 1;
// If numMoved is 0, it means that the element to be deleted happens to be the last element of the array
            if (numMoved == 0)
// Overwrite the original array
                setArray(Arrays.copyOf(elements, len - 1));
            else
// If numMoved is not 0, define a new array with the length of the original array -1
                Object[] newElements = new Object[len - 1];
//Copy the element at 0~index to the new array
                System.arraycopy(elements, 0, newElements, 0, index);
//Move index + 1~the last element of the array to the new array
                System.arraycopy(elements, index + 1, newElements, index,
                                 numMoved);
// Overwrite the original array
                setArray(newElements);
            }
// Return the deleted element value
            return oldValue;
        } finally {
// After the operation is completed, release the lock lock
            lock.unlock();
        }
    }

4. Summary

After the above analysis of the add method, the get method, the set modification method, and the remove method, students can actually see that it is nothing more than adding a lock to the original ArrayList collection.

When doing the three operations of adding, modifying, and deleting, you can achieve thread safety by combining it with the idea of copying an array. This is the core design idea of CopyOnWriteArrayList thread safety, which is not particularly difficult to understand. .