Skip to main content

CopyOnWriteArrayList

huhxAbout 2 minjavaConcurrency-CollectionConcurrency

ReadWriteLock这个锁是读写不能同时进行,java中提供的CopyOnWriteArrayList做的更狠,读与写可以同步进行,只是写与写之间会阻塞,这样就进一步的提高读取的性能了。而关于CopyOnWriteArrayList是怎么做到如此的,正是我们这里需要解答的。

使用

当我们为CopyOnWriteArrayList创建迭代器时,我们会在调用iterator()时获得列表中数据的不可变快照。

public class CopyOnWriteArrayListMain {
    public static void main(String[] args) throws InterruptedException {
        var list = new CopyOnWriteArrayList<>(List.of("0"));

        var iterator = list.iterator();
        list.add("1");
        iterator.forEachRemaining(System.out::println);

        System.out.println(Arrays.toString(list.toArray()));
    }
}

输出结果如下:

0
[0, 1]

分析

对于CopyOnWriteArrayList来说,读取是完全不用加锁的,并且更好的消息是:写入也不会阻塞读取操作,只有写入和写入之间需要进行同步等待。

从这个类名字CopyOnWrite我们知道,所谓CopyOnWrite就是在写入操作时会进行一次自我复制。也就是说当这个List需要修改时,并不修改原有的内容,而是对原有的数据进行 一次复制,将修改的内容写入副本中。写完之后,再将修改完的副本替换原来的数据。这样就可以保证写操作不会影响读了

有关读取的代码实现:

private transient volatile Object[] array;

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

static <E> E elementAt(Object[] a, int index) {
    return (E) a[index];
}

public E get(int index) {
    return elementAt(getArray(), index);
}
 












读取代码没有任何同步控制和锁操作,就是普通的根据index取数组里面的元素。相对而言,对于写而言则较之复杂些:

final transient Object lock = new Object();

public boolean add(E e) {
    synchronized (lock) {
        Object[] es = getArray();
        int len = es.length;
        es = Arrays.copyOf(es, len + 1);
        es[len] = e;
        setArray(es);
        return true;
    }
}








 



写入操作首先得获取对象锁,然后对现有的列表A进行一次复制。对复制后的列表B进行相应的写入的操作,写入完成之后,再把列表B覆盖列表A。

FAQ

总结

  • CopyOnWriteArrayList合适读多写少的场景
  • CopyOnWriteArrayList列表不应太大,因为每次写入会进行一次复制

参考