工作原理
CopyOnWriteArrayList使用了写时复制方法,当执行add(E e)添加新元素时,会先将原有数组进行copy,然后在copy的新数组中进行写操作,写完之后,将原来数组引用指向新数组。
添加新元素时,copy新数组,引用仍然指向原数组,如下图
新元素添加成功后,改变引用指向,指向新数组,如下图
源代码
如下为执行add时的源代码
@Override
public boolean add(E object) {
synchronized (CopyOnWriteArrayList.this) {
add(slice.to - slice.from, object);
return true;
}
}
public synchronized boolean add(E e) {
Object[] newElements = new Object[elements.length + 1];
//进行数组copy
System.arraycopy(elements, 0, newElements, 0, elements.length);
newElements[elements.length] = e;
elements = newElements;
return true;
}
由如上代码可看出:
进行写操作时,是加锁synchronized的,并发的进行写操作是线程安全的。
进行读操作时,可根据写操作时操作未完成、操作已完成但引用指向未修改、写操作完成这三种情况进行考虑。根据读取当前引用指向的原则来确定是读取的copy前还是copy后数组。
CopyOnWriteArrayList的读操作是可以不用加锁的。
缺点
数据一致性问题:根据其过程分析,只有当add完毕并且引用指向新数组后,读取的数据才是最新数组数据。因此能保证最终数据一致,但无法保证add过程中的数据一致。
内存占用问题:add过程中,是做了一份copy,写操作时有两个对象驻存在内存中(注意:在复制的时候只是复制容器里的引用,只是在写的时候会创建新对象添加到新容器里,而旧容器的对象还在使用,所以有两份对象内存),占用内存翻倍,如果本身占用内存较大,并且add的内存也较大,就会导致频繁的Young Gc和Full Gc。
适用场景
适合读多写少的场景,不能用于实时读的场景。如黑名单、白名单、商品种类的访问和更新等情况。
注:
图片源自https://blog.csdn.net/linsongbin1/article/details/54581787
网友评论