定义
Copy-On-Write(写时复制)简称COW,是一种并发场景下共享同一数据的策略。
顾名思义,它的基本思想是大家共享同一份数据,读的时候不加锁,直接读取数据;只有在要更新的时候,把数据先拷贝一份,在这个新拷贝的数据上演绎更新操作,然后再用这个新数据替换老数据。
核心思想
java中的CopyOnWrite容器就是这种思想策略的具体实现,例如CopyOnWriteArrayList,CopyOnWriteArraySet。
读取容器中的数据时,不加锁,可以并发读。而当我们调用容器的add方法时,不直接往当前的容器添加元素,而是加锁并复制一份新的容器出来,往新容器添加元素后,再将原容器的引用指向新容器。
其实这也是一种读写分离思想在并发场景下的应用,在平时的开发中经常会用到,非常重要。
源码
为了更好的理解,我们来扒一扒CopyOnWriteArrayList的核心源码:
首先CopyOnWriteArrayList是一个模板类,实现了List容器接口
public class CopyOnWriteArrayList<E> implements List<E>
它的构成很简单,就两个变量:并发锁一枚,数组一个
/** The lock protecting all mutators */
final transient ReentrantLock lock = new ReentrantLock();
/** The array, accessed only via getArray/setArray. */
private transient volatile Object[] array;
读取也很简单,直接通过下标查数组
private E get(Object[] a, int index) {
return (E) a[index];
}
public E get(int index) {
return get(getArray(), index);
}
我们看下最核心的add,就是加锁-复制-更新-替换
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();
}
}
而替换后让所有线程都能拿到最新的共享数据则是通过volatile关键字来实现的,这个我们后面有机会再细聊。
应用场景
显然,读写分离的CopyOnWrite容器通常都用于读多写少的并发场景,例如黑白名单、目录等等。
CopyOnWrite容器的缺点也同样明显,要注意扩容开销导致的内存问题,尽量存入一些小对象,避免大量的写操作。
同时,CopyOnWrite容器只能保证数据的最终一致性,无法做到实时。因为当线程复制更新的瞬间,其他线程只能读到老容器内的数据。
学完,收工~