CopyOnWriteArrayList写时复制原理与源码简析

99 阅读2分钟

CopyOnWriteArrayList写时复制原理

Java JUC 并发包中,关于 List 容器的并发安全的实现类只有一个,就是 CopyOnWriteArrayList。

CopyOnWrite,顾名思义就是写的时候会将共享变量新复制一份出来,具体的原理是

  • CopyOnWriteArrayList 内部维护了一个数组成员变量 array
  • 所有的读操作都是基于 array 进行的
  • 而每次写操作时,都会会将 array 复制一份,然后在新复制处理的数组上执行增加元素的操作,执行完之后再将 array 指向这个新的数组。

CopyOnWriteArrayList的特性

  • 所有的读操作都是无锁的

  • 写操作是加锁的,只能串行执行

  • 读操作和写操作可以并行执行,因为读操作是基于原array,而写操作则是基于新array。

  • 同理,迭代器遍历也是线程安全的,因为遍历操作一直都是基于原 array ,而写操作则是基于新 array 。

使用注意事项

  1. CopyOnWriteArrayList 仅适用于写操作非常少的场景,因为它的写操作是加锁互斥执行的,且执行时还需要执行拷贝数组操作。

  2. CopyOnWriteArrayList 迭代器是只读的,不支持增删改。因为迭代器遍历的仅仅是一个快照,而对快照进行增删改是没有意义的。

部分源码介绍

// get操作是无锁的,getArray方法就是获取当前array数组
public E get(int index) {
    return get(getArray(), index);
}
// add方法是加锁互斥执行的
// 且会拷贝一份新数组newElements
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();
    }
}
// 获取的迭代器也是基于当前数组array
public Iterator<E> iterator() {
    return new COWIterator<E>(getArray(), 0);
}
// 迭代器不支持增删改操作,因为迭代器遍历的仅仅是一个快照,而对快照进行增删改是没有意义的。
static final class COWIterator<E> implements ListIterator<E> {
    public void remove() {
        throw new UnsupportedOperationException();
    }

    public void set(E e) {
        throw new UnsupportedOperationException();
    }

    public void add(E e) {
        throw new UnsupportedOperationException();
    }
}

参考资料: