背景
CopyOnWriteArrayList容器是Collections.synchronizedList(List list)的替代方案。
CopyOnWriteArrayList在某些情况下具有更好的性能(读操作远多于写操作时)。在传统的使用 synchronized 关键字以保证互斥操作的实现中(比如 Vector),通常会把读操作进行加锁,因而任一时刻只能有一个读线程能够获得锁,所以其他的读线程都必须等待,大大影响性能。
CopyOnWriteArrayList称为“写时复制”容器,就是在多线程对容器对象进行读写操作时,执行写操作的线程在写的同时,还会把容器复制一份(并对复制后的容器实例进行写修改操作),最终就可以做到写操作进行的同时,不阻塞其他线程的读操作。
从JDK1.5开始Java并发包里提供了两个使用CopyOnWrite机制实现的并发容器,它们是CopyOnWriteArrayList和CopyOnWriteArraySet。
CopyOnWriteArrayList
CopyOnWriteArrayList实现
先说说CopyOnWriteArrayList容器的实现原理。
简单地说,就是在需要对容器进行操作的时候,将容器拷贝一份,对容器的修改操作都在容器的拷贝副本中进行。当修改操作结束,再用容器的拷贝副本替换掉原来的容器。
这样设计的好处是实现了读写分离,并且读读时不会发生阻塞。
下面的源码是CopyOnWriteArrayList的add方法实现:
public void add(int index, E element) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
if (index > len || index < 0)
throw new IndexOutOfBoundsException("Index: "+index+
", Size: "+len);
Object[] newElements;
int numMoved = len - index;
//1、复制出一个新的数组
if (numMoved == 0)
newElements = Arrays.copyOf(elements, len + 1);
else {
newElements = new Object[len + 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index, newElements, index + 1,
numMoved);
}
//2、把新元素添加到新数组中
newElements[index] = element;
//3、把数组指向原来的数组
setArray(newElements);
} finally {
lock.unlock();
}
}
上面的三个步骤实现了写时复制的思想,在读数据的时候不会锁住list,因为写操作是在原来容器的拷贝上进行的。而且,可以看到,如果对容器拷贝修改的过程中又有新的读线程进来,那么读到的还是旧的数据。读的代码如下:
public E get(int index) {
return get(getArray(), index);
}
final Object[] getArray() {
return array;
}
适用场景
CopyOnWriteArrayList 允许在写操作的同时进行读操作,因而大大提高了读操作的性能,因此很适合读多写少的应用场景,比如白名单,黑名单,商品类目的访问和更新场景,而不适合内存敏感以及对实时性要求很高的场景。
CopyOnWriteArrayList容器允许并发读,读操作是无锁的,性能较高。至于写操作,比如向容器中添加一个元素,则首先将当前容器复制一份,然后在新副本上执行写操作,结束之后再将原容器的引用指向新容器。
优缺点分析
了解了CopyOnWriteArrayList的实现原理,分析它的优缺点及使用场景就很容易了。
优点:
读操作性能很高,因为无需任何同步措施,比较适用于读多写少的并发场景。Java的list在遍历时,若中途有别的线程对list容器进行修改,则会抛出ConcurrentModificationException异常。而CopyOnWriteArrayList由于其"读写分离"的思想,遍历和修改操作分别作用在不同的list容器,所以在使用迭代器进行遍历时候,也就不会抛出ConcurrentModificationException异常了
缺点:
缺点也很明显:
- 一是内存占用问题,毕竟每次执行写操作都要将原容器拷贝一份,数据量大时,对内存压力较大,可能会引起频繁GC;
- 二是无法保证数据不一致,Vector对于读写操作均加锁同步,可以保证读和写的强一致性。而CopyOnWriteArrayList由于其实现策略的原因,写和读分别作用在新老不同容器上,在写操作执行过程中,读虽然不会阻塞,但读取到的却是老容器的数据(或者说是旧数据)。
CopyOnWriteArraySet
原理:基于CopyOnWriteArrayList实现,其唯一的不同是在add时调用的是CopyOnWriteArrayList的addIfAbsent方法,其遍历当前Object数组,如Object数组中已有了当前元素,则直接返回,如果没有则放入Object数组的尾部,并返回。
Reference
- CopyOnWriteArrayList实现原理及源码分析 - https://www.cnblogs.com/chengxiao/p/6881974.html