Java并发编程-CopyOnWriteArrayList原理

1.CopyOnWriteArrayList是一个ArrayList线程安全的变体,它的所有改变底层数组的方法如(add,set等)通过复制底层数组来实现。这原本很消耗性能,但当遍历操作远远多于修改操作时,可能会比其他方法更高效,当你不想同步遍历操作时这种实现可能会有用。你需要排除并发线程之间的冲突。快照形式的迭代方法在得迭代器创建时使用一个数组状态的引用。这个数组在迭代器的生命周期内绝不会改变,所以多线程之间的冲突是不可能发生的,迭代器保证不会抛出ConcurrentModificationException。自从迭代器创建以来,迭代器不会反映对该list的增加,删除,或者改变。迭代器不支持数组元素的操作,这些方法会抛出UnsupportedOperationException。和其他并发集合一样,一个将一个对象放入CopyOnWriteArrayList的线程happen-before于另一个线程中访问或者删除CopyOnWriteArrayList中的元素。

2.CopyOnWriteArrayList类关系图

                             Java并发编程-CopyOnWriteArrayList原理

       由图可以看出CopyOnWriteArrayList实现了Serializable,RandomAccess,Cloneable,List,Collection和Iterable接口。所以CopyOnWriteArrayList支持随机访问,List和Collection接口的所有方法以及可以通过迭代器进行遍历操作。

3.CopyOnWriteArrayList核心字段

   private static final long serialVersionUID = 8673264195747942595L;

    /** 保护所有操作的锁 */
    transient final ReentrantLock lock = new ReentrantLock();

    /** 底层数据结构就是数组*/
    private volatile transient Object[] array;

4.修改数组的相关方法实现

public E set(int index, E element) {
        final ReentrantLock lock = this.lock;
        // 加锁
        lock.lock();
        try {
            // 获取原始数组
            Object[] elements = getArray();
            E oldValue = get(elements, index);

            if (oldValue != element) {
                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 oldValue;
        } finally {
            lock.unlock();
        }
    }

public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            // 拷贝原数组
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            // 在新数组中添加元素
            newElements[len] = e;
            // 将原来数组的引用更新为新数组
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

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;
            // 如果不需要移动则说明是删除的最后一个元素,直接将原数组从0到len-1拷贝一份
            if (numMoved == 0)
                setArray(Arrays.copyOf(elements, len - 1));
            else {
                // 创建一个比原数组长度小1的新数组
                Object[] newElements = new Object[len - 1];
                // 将原数组从0到index,index + 1到len的元素拷贝到新数组
                System.arraycopy(elements, 0, newElements, 0, index);
                System.arraycopy(elements, index + 1, newElements, index,
                                 numMoved);
                // 更新数组引用
                setArray(newElements);
            }
            return oldValue;
        } finally {
            lock.unlock();
        }
    }