概述

一种线程安全的 ArrayList,其中所有的修改操作都是通过生成底层数组的副本来实现的。虽然这种成本较高,但对于遍历操作较多,写并发较小的场景,使用这个并发集合会非常合适。快照模式能保证所创建的迭代器,在迭代器的生命周期内数据永远不会发生更改。

重要的变量

  1. // java.util.concurrent.CopyOnWriteArrayList
  2. public class CopyOnWriteArrayList<E>
  3. implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
  4. // CopyOnWriteArrayList 全局变量锁,所有的变更操作都需要先获得这把锁
  5. final transient ReentrantLock lock = new ReentrantLock();
  6. // 存放数据的数组
  7. private transient volatile Object[] array;
  8. ...
  9. }

变量非常少,只有两个,一个用于存放数据,另一个就是全局锁。在所有的修改操作在执行前,都需要获得这把锁。

构造函数

  1. public CopyOnWriteArrayList() {
  2. setArray(new Object[0]);
  3. }
  4. public CopyOnWriteArrayList(Collection<? extends E> c) {
  5. Object[] elements;
  6. if (c.getClass() == CopyOnWriteArrayList.class)
  7. elements = ((CopyOnWriteArrayList<?>)c).getArray();
  8. else {
  9. elements = c.toArray();
  10. if (c.getClass() != ArrayList.class)
  11. elements = Arrays.copyOf(elements, elements.length, Object[].class);
  12. }
  13. setArray(elements);
  14. }
  15. public CopyOnWriteArrayList(E[] toCopyIn) {
  16. setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
  17. }

CopyOnWriteArrayList 默认的数组初始化长度为 0

源码解析

添加操作:add

  1. // java.util.concurrent.CopyOnWriteArrayList#add(E)
  2. public boolean add(E e) {
  3. final ReentrantLock lock = this.lock;
  4. lock.lock();
  5. try {
  6. Object[] elements = getArray();
  7. int len = elements.length;
  8. // #1 复制数组,长度 + 1
  9. Object[] newElements = Arrays.copyOf(elements, len + 1);
  10. newElements[len] = e;
  11. // #2 修改指针
  12. setArray(newElements);
  13. return true;
  14. } finally {
  15. lock.unlock();
  16. }
  17. }

获取迭代器:iterator

public Iterator<E> iterator() {
    return new COWIterator<E>(getArray(), 0);
}

快照:COWIterator

static final class COWIterator<E> implements ListIterator<E> {
    /** Snapshot of the array */
    private final Object[] snapshot;

    // 遍历游标
    private int cursor;

    // 构造函数
    private COWIterator(Object[] elements, int initialCursor) {
        cursor = initialCursor;
        snapshot = elements;
    }    
    ...
}

这里建立的快照就是保存对原数组的引用,原数组肯定是不会修改的。因为修改操作会重新创建一个新的数组。