[JAVA collection articles] Detailed Explanation of CopyOnWriteArrayList


Introduction

CopyOnWriteArrayList is a thread-safe version of ArrayList. It is also implemented internally through arrays. Every time the array is modified, a new array is completely copied for modification. After modification, the old array is replaced, which ensures that only write operations are blocked, and read operations are not blocked. operation to achieve read-write separation.

inheritance system

public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable 
    {
    
    ...}
  • CopyOnWriteArrayList implements List, RandomAccess, Cloneable, java.io.Serializable and other interfaces.

  • CopyOnWriteArrayList implements List and provides basic operations such as adding, deleting, and traversing.

  • CopyOnWriteArrayList implements RandomAccess, which provides random access capabilities.

  • CopyOnWriteArrayList implements Cloneable and can be cloned.

  • CopyOnWriteArrayList implements Serializable and can be serialized.

Source code analysis

Attributes

/** 用于修改时加锁 */
final transient ReentrantLock lock = new ReentrantLock();

/** 真正存储元素的地方,只能通过getArray()/setArray()访问 */
private transient volatile Object[] array;
  • lock: lock when modifying, and use transient modification to indicate that it will not be automatically serialized.

  • array: the place where elements are stored, use transient modification to indicate no automatic serialization, use volatile modification to indicate that the modification of this field by one thread is immediately visible to another thread.

Construction method

Create an empty array.

public CopyOnWriteArrayList() {
    
    
    // 所有对array的操作都是通过setArray()和getArray()进行
    setArray(new Object[0]);
}

final void setArray(Object[] a) {
    
    
    array = a;
}
public CopyOnWriteArrayList(Collection<? extends E> c) {
    
    
    Object[] elements;
    if (c.getClass() == CopyOnWriteArrayList.class)
        // 如果c也是CopyOnWriteArrayList类型
        // 那么直接把它的数组拿过来使用
        elements = ((CopyOnWriteArrayList<?>)c).getArray();
    else {
    
    
        // 否则调用其toArray()方法将集合元素转化为数组
        elements = c.toArray();
        // 这里c.toArray()返回的不一定是Object[]类型
        // 详细原因见ArrayList里面的分析
        if (elements.getClass() != Object[].class)
            elements = Arrays.copyOf(elements, elements.length, Object[].class);
    }
    setArray(elements);
}
  • If c is of CopyOnWriteArrayList type, directly assign its array to the current list array. Note that this is a shallow copy, and the two collections share the same array.

  • If c is not of CopyOnWriteArrayList type, copy all the elements of c to the current list array.

public CopyOnWriteArrayList(E[] toCopyIn) {
    
    
    setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
}
  • Copy the elements of toCopyIn to the array of the current list.

add(E e) method

Add an element to the end.

public boolean add(E e) {
    
    
    final ReentrantLock lock = this.lock;
    // 加锁
    lock.lock();
    try {
    
    
        // 获取旧数组
        Object[] elements = getArray();
        int len = elements.length;
        // 将旧数组元素拷贝到新数组中
        // 新数组大小是旧数组大小加1
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        // 将元素放在最后一位
        newElements[len] = e;
        setArray(newElements);
        return true;
    } finally {
    
    
        // 释放锁
        lock.unlock();
    }
}
  1. lock;

  2. Get an array of elements;

  3. Create a new array whose size is the length of the original array plus 1, and copy the elements of the original array to the new array;

  4. Put the newly added element at the end of the new array;

  5. Assign the new array to the array property of the current object, overwriting the original array;

  6. unlock.

add(int index, E element) method

Adds an element at the specified index.

public void add(int index, E element) {
    
    
    final ReentrantLock lock = this.lock;
    // 加锁
    lock.lock();
    try {
    
    
        // 获取旧数组
        Object[] elements = getArray();
        int len = elements.length;
        // 检查是否越界, 可以等于len
        if (index > len || index < 0)
            throw new IndexOutOfBoundsException("Index: "+index+
                                                ", Size: "+len);
        Object[] newElements;
        int numMoved = len - index;
        if (numMoved == 0)
            // 如果插入的位置是最后一位
            // 那么拷贝一个n+1的数组, 其前n个元素与旧数组一致
            newElements = Arrays.copyOf(elements, len + 1);
        else {
    
    
            // 如果插入的位置不是最后一位
            // 那么新建一个n+1的数组
            newElements = new Object[len + 1];
            // 拷贝旧数组前index的元素到新数组中
            System.arraycopy(elements, 0, newElements, 0, index);
            // 将index及其之后的元素往后挪一位拷贝到新数组中
            // 这样正好index位置是空出来的
            System.arraycopy(elements, index, newElements, index + 1,
                             numMoved);
        }
        // 将元素放置在index处
        newElements[index] = element;
        setArray(newElements);
    } finally {
    
    
        // 释放锁
        lock.unlock();
    }
}
  1. lock;

  2. Check whether the index is legal, and throw an exception if it is not legal IndexOutOfBoundsException. Note that it is also legal for index to be equal to len;

  3. If the index is equal to the length of the array (that is, the last digit of the array plus 1), then copy an len+1array;

  4. If the index is not equal to the length of the array, create a len+1new array and divide it into two parts according to the index position. The part before the index (not included) is copied to the part before the index (not included) of the new array, and the position after the index (included) Copy to the position after (not including) the index of the new array, and leave the position where the index is located;

  5. Assign the index position as the element to be added;

  6. Assign the new array to the array property of the current object, overwriting the original array;

  7. unlock;

addIfAbsent(E e) method

Adds an element if the element does not exist in the collection.

public boolean addIfAbsent(E e) {
    
    
    // 获取元素数组, 取名为快照
    Object[] snapshot = getArray();
    // 检查如果元素不存在,直接返回false
    // 如果存在再调用addIfAbsent()方法添加元素
    return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
        addIfAbsent(e, snapshot);
}

private boolean addIfAbsent(E e, Object[] snapshot) {
    
    
    final ReentrantLock lock = this.lock;
    // 加锁
    lock.lock();
    try {
    
    
        // 重新获取旧数组
        Object[] current = getArray();
        int len = current.length;
        // 如果快照与刚获取的数组不一致
        // 说明有修改
        if (snapshot != current) {
    
    
            // 重新检查元素是否在刚获取的数组里
            int common = Math.min(snapshot.length, len);
            for (int i = 0; i < common; i++)
                // 到这个方法里面了, 说明元素不在快照里面
                if (current[i] != snapshot[i] && eq(e, current[i]))
                    return false;
            if (indexOf(e, current, common, len) >= 0)
                    return false;
        }
        // 拷贝一份n+1的数组
        Object[] newElements = Arrays.copyOf(current, len + 1);
        // 将元素放在最后一位
        newElements[len] = e;
        setArray(newElements);
        return true;
    } finally {
    
    
        // 释放锁
        lock.unlock();
    }
}
  1. Check if this element exists in the array snapshot;

  2. If it exists, return false directly, if it does not exist, call addIfAbsent(E e, Object[] snapshot)processing;

  3. lock;

  4. If the current array is not equal to the incoming snapshot, it means there is a modification, check whether the element to be added exists in the current array, and return false if it exists;

  5. Copy a new array whose length is equal to the length of the original array plus 1, and copy the elements of the original array to the new array;

  6. Add the new element to the last bit of the array;

  7. Assign the new array to the array property of the current object, overwriting the original array;

  8. unlock;

get(int index)

Get the element at the specified index, support random access, and the time complexity is O(1).

public E get(int index) {
    
    
    // 获取元素不需要加锁
    // 直接返回index位置的元素
    // 这里是没有做越界检查的, 因为数组本身会做越界检查
    return get(getArray(), index);
}

final Object[] getArray() {
    
    
    return array;
}

private E get(Object[] a, int index) {
    
    
    return (E) a[index];
}
  1. Get an array of elements;

  2. Returns the element at the specified index position of the array;

remove(int index) method

Delete the element at the specified index position.

public E remove(int index) {
    
    
    final ReentrantLock lock = this.lock;
    // 加锁
    lock.lock();
    try {
    
    
        // 获取旧数组
        Object[] elements = getArray();
        int len = elements.length;
        E oldValue = get(elements, index);
        int numMoved = len - index - 1;
        if (numMoved == 0)
            // 如果移除的是最后一位
            // 那么直接拷贝一份n-1的新数组, 最后一位就自动删除了
            setArray(Arrays.copyOf(elements, len - 1));
        else {
    
    
            // 如果移除的不是最后一位
            // 那么新建一个n-1的新数组
            Object[] newElements = new Object[len - 1];
            // 将前index的元素拷贝到新数组中
            System.arraycopy(elements, 0, newElements, 0, index);
            // 将index后面(不包含)的元素往前挪一位
            // 这样正好把index位置覆盖掉了, 相当于删除了
            System.arraycopy(elements, index + 1, newElements, index,
                             numMoved);
            setArray(newElements);
        }
        return oldValue;
    } finally {
    
    
        // 释放锁
        lock.unlock();
    }
}
  1. lock;

  2. Get the old value of the element at the specified index position;

  3. If the last element is removed, copy the first len-1 elements of the original array to the new array, and assign the new array to the array property of the current object;

  4. If the removed element is not the last one, create a new array with a length of len-1, and copy all the elements of the original array except the specified index position to the new array, and assign the new array to the array property of the current object;

  5. Unlock and return the old value;

size() method

Returns the length of the array.

public int size() {
    
    
    // 获取元素个数不需要加锁
    // 直接返回数组的长度
    return getArray().length;
}

ask questions

Why CopyOnWriteArrayList has no size property?

Because each modification is to copy an array that can store the target number of elements, so the size attribute is not needed. The length of the array is the size of the collection, unlike the length of the ArrayList array that is actually larger than the size of the collection. For example, the add(E e) operation first copies an array of n+1 elements, and then puts the new element in the last position of the new array. At this time, the length of the new array is len+1, which is the set size up.

Summarize

  1. CopyOnWriteArrayList is ReentrantLocklocked with a reentrant lock to ensure thread safety;

  2. The write operation of CopyOnWriteArrayList must first copy a new array, modify it in the new array, and replace the old array with the new array after the modification, so the space complexity is O(n), and the performance is relatively low;

  3. The read operation of CopyOnWriteArrayList supports random access, and the time complexity is O(1);

  4. CopyOnWriteArrayList adopts the idea of ​​read-write separation, the read operation is not locked, the write operation is locked, and the write operation takes up a large memory space, so it is suitable for occasions with more reads and less writes;

  5. CopyOnWriteArrayList only guarantees final consistency, not real-time consistency.

Guess you like

Origin blog.csdn.net/jiang_wang01/article/details/131257609