CopyOnWrite(COW)是在写操作的时候copy当前数据,然后在写完数据之后设置成新的数据。适用于读多写少的并发场景。
CopyOnWrite 使用了 ReentrantLock(支持重入的独占锁) 来支持并发操作。ReentrantLock 是一种支持重入的独占锁,任意时刻只允许一个线程来获得锁。ReentrantLock 默认是非公平锁(即不按照进入等待队列的顺序唤醒线程)机制。
本质是一种延时策略,只有在真正需要复制的时候才复制,而不是提前复制好。
综上:在写入数据时使用ReentrantLock进行加锁,保证每一次写操作都是一个线程完成的,且每一次写操作都会先拷贝一份数据,然后再对新数组进行写操作;在读取数据的时候没有加锁,因此是允许多个线程进入访问的。所以,CopyOnWrite天生 适合读多写少的场景。
数组的遍历 private final Object[] snapshot; // 数组快照 private int cursor; // 遍历的时候会先获得当前数组对象的一个拷贝,即生成一个快照,接着遍历操作在快照上进行。 public Iterator<E> iterator(){ return COWIterator<E>(getArray(),0); } public void testCOWIterator(){ CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList(); list.add("xiaoming"); list.add("hanmeimei"); Iterator<String> iterator = list.iterator(); list.add("kangkang"); while(iterator.hasNext()){ syso(iterator.next()); } // out xiaoming hanmeimei }综上:CopyOnWrite使用迭代器遍历时,我们只是获取当前list的一个快照,在对list获取迭代器后,再对list进行各种操作,是不会影响快照的遍历的,即每个线程都将获得当前数组的一个快照,所以迭代器不需要加锁就可以安全的实现遍历。
应用于读多写少的并发场景。例如:白名单,黑名单,往往很长时间才会进行修改,大部分情况下我们只需要读数据。
CopyOnWrite翻译来讲的话就是写时复制,就是我们往容器添加数据的时候不是直接添加,而是先将当前容器拷贝到新的容器中,然后往新的容器里面添加数据,最后在原容器的引用指向新的容器。因此,在并发读的情况下我们不需要加锁,因为当前的容器并不会被修改。
CopyOnWrite的优缺点:
优点:只在并发写的时候才会加锁,访问与读取不需要加锁,性能上比较高缺点: 由于每次写数据时会复制整个数组,若容器对象占用的内存比较大时,容易发生GC。因此如果我们想用这个COW机制的话,数据不能太大,修改机会也比较少;通过上面的源码分析,不难看出COW只能保证数据最终的一致性,不能保证数据的实时一致性。当我们碰到写入的数据需要立马读取到的话,就不要用CWO了。最后说一句: CopyOnWriteArrayList 在修改的时候会复制整个数组,所以如果容器经常被修改或者这个数组本身就非常大的时候,是不建议使用的。反之,如果修改非常少,数组量又不大,并且对读性能要求比较高的场景,使用COW容器的效果就比较好了。