CopyOnWriteArrayList
About 2 min
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
列表不应太大,因为每次写入会进行一次复制