背景
Collections.synchronizedList()、Vector虽然线程安全,但是使用的synchronized锁实现,锁粒度较粗,且迭代时候不允许修改,否则会抛出异常,鉴于这种情况,java.util.concurrent包下提供了CopyOnWriteArrayList、CopyOnWriteSet一类的并发集合,本文以CopyOnWriteArrayList为例,简单聊聊这个线程安全且效率高一些的并发容器。
CopyOnWriteArrayList特点
- 线程安全,支持迭代中修改集合;
- 支持并发读及并发读写,但是并发写需要阻塞同步,从这点看CopyOnWriteArrayList比较适合读多写少情况;
copyOnWriteArrayList=[A, B, C, D]
当前值=A
copyOnWriteArrayList=[A, B, C]
当前值=B
copyOnWriteArrayList=[A, B, C]
当前值=C
copyOnWriteArrayList=[A, B, C, F]
当前值=D
可以发现,CopyOnWriteArrayList不同于ArrayList,在迭代中支持修改,"F"被添加进了数组且"D"被安全移除,但是最后一次打印iterator.next()方法输出的还是被移除的"D"而不是"F",原因在于iterator对象是的获取是在"F"被添加之前,要想顺利获取“F”元素,则在添加元素之后再次获取迭代对象即可。
CopyOnWriteArrayList源码分析
CopyOnWriteArrayList的关键在于会将数组拷贝一份,真正操作的是数组的副本,这一点和java内存模型(工作内存只操作变量的朱内存副本拷贝)、ThreadLocal类(线程本地存储)有很大的类比性,也是CopyOnWriteArrayList为什么是线程安全的核心。
CopyOnWriteArrayList的核心方法就是add(),简单分析下做了哪些事情:
- 使用ReentrantLock上锁;
- 得到当前数组对象,并且复制一个新数组;
- 将待添加元素放入新数组中;
- 将CopyOnWriteArrayList中的数组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();
}
}
CopyOnWriteArrayList不足之处
- CopyOnWrite思想,导致数据会存在两份,占用空间变多;