CopyOnWriteArrayList:Java集合中的读写分离
提到读写分离,大家可能首先会想到MySQL的读写分离,也就是在master节点上进行数据库写操作,在slave节点上进行数据库读操作,用这样的手段来提升数据库的性能、稳定性、高并发。其实,在java编程语言中,有一个集合类也贯彻了读写分离的思想,它就是:CopyOnWriteArrayList (另外一个类CopyOnWriteArraySet与此类似)。
一、CopyOnWriteArrayList总体介绍
我们先来看一下此集合类的层级结构:
CopyOnWriteArrayList类实现了List接口,是ArrayList一种线程安全的变体,所有的可变操作(增删改等),都是通过复制底层数组来进行的。官方文档说明如下:
A thread-safe variant of ArrayList in which all mutative operations (add, set, and so on) are implemented by making a fresh copy of the underlying array.
二、CopyOnWriteArrayList实现原理
通过源码看一下类的底层实现原理,以下是类变量:
//可重入锁,对所有可变操作加锁
final transient ReentrantLock lock = new ReentrantLock();
//存储数据的数组,保证可见性。只有getArray和setArray方法可以访问
private transient volatile Object[] array;
向容器中添加元素的源码如下:
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();
}
}
可以看出,在开始添加元素前,先进行加锁操作,然后获取当前容器底层的数组数据,通过Arrays.copyOf方法复制到一个新的数组中,添加元素后把新的数组赋值给容器底层的数组,最后释放锁。其它的可变操作,比如删除、元素替换等,实现机制是相同的,都是通过复制一个新的数组来进行的。
读取操作比较简单,直接从数组中获取数据,没有进行加锁:
public E get(int index) {
return get(getArray(), index);
}
因为写操作总是复制底层数组来进行运算,而读操作直接读取数组中数据,所以这是一种读写分离的形式。当然,因为每次可变操作都要进行数组的复制,这个代价是相当高的,所以CopyOnWriteArrayList比较适合读多写少的场合,比如商品品类,省市区等地址信息等。
三、与ArrayList的对比
既然CopyOnWriteArrayList是ArrayList的变体,那么它们肯定有区别,下面的表格从多个角度对比了两者不一样的地方:
CopyOnWriteArrayList | ArrayList |
---|---|
线程安全 | 线程不安全 |
效率高 | 效率低 |
一个线程在遍历容器的时候,其它线程可以对容器进行修改,因为是在一个新的数组上操作,所以CopyOnWriteArrayList不会抛出ConcurrentModificationException | 一个线程在遍历容器的时候,如果其它线程对容器进行修改,那么会抛出ConcurrentModificationException |
fast-safe | fast-fail |
属于 java.util.concurrent包 | 属于java.util包 |
java 1.5 引进 | fast-fail |
fast-safe | java 1.2 引进 |
参考资料:
http://www.benchresources.net/copyonwritearraylist-vs-arraylist-in-java/
http://www.makeinjava.com/copyonwritearraylist-concurrent-collection-java-example/
欢迎关注微信公众号,获取更多信息。