头图

Introduction

CopyOnWriteArrayList is a thread-safe version of ArrayList. It is also implemented internally through arrays. Every time an array is modified, a new array is copied to modify it. After modification, the old array is replaced. This ensures that only write operations are blocked, and reads 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 interfaces such as List, RandomAccess, Cloneable, java.io.Serializable, etc.
  • CopyOnWriteArrayList implements List and provides basic operations such as adding, deleting, and traversing.
  • CopyOnWriteArrayList implements RandomAccess and 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 modified, use transient modification to indicate that it is not automatically serialized.
  • Array: Where elements are stored, use transient modification to indicate that they are not automatically serialized, and use volatile modification to indicate that changes to this field by one thread are 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 type CopyOnWriteArrayList, directly assign its array to the array of the current list. Note that this is a shallow copy, and the two sets share the same array.
  • If c is not of type CopyOnWriteArrayList, copy all elements of c to the array of the current list.
public CopyOnWriteArrayList(E[] toCopyIn) {
    setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
}
  • Copies the elements of toCopyIn to the current list array.

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 element array;
  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. If it is illegal, throw IndexOutOfBoundsException exception. Note that index equal to len is also legal;
  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 array of len+1 ;
  4. If the index is not equal to the length of the array, create a new array of len+1 and divide it into two parts according to the index position. The position is copied to the position after (excluding) the index of the new array, and the position of the index is left blank;
  5. Assign the index position to 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) for 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, if it exists, return false directly;
  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 a 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)

Gets the element at the specified index, supports random access, and has a time complexity of 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 element array;
  2. Returns the element at the specified index position of the array;

remove(int index) method

Removes 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 element, create a new array of len-1 length, 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 doesn't CopyOnWriteArrayList have a size property?

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

Summarize

  1. CopyOnWriteArrayList uses ReentrantLock reentrant lock to lock to ensure thread safety;
  2. The write operation of CopyOnWriteArrayList must first copy a new array, make modifications in the new array, and then 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 separation of read and write, the read operation is not locked, the write operation is locked, and the write operation occupies a large memory space, so it is suitable for occasions where more reads and writes less;
  5. CopyOnWriteArrayList only guarantees eventual consistency, not real-time consistency.

初念初恋
175 声望17 粉丝