title: JUC.CopyOnWriteArrayList
CopyOnWriteArrayList提纲
CopyOnWriteArrayList简介
CopyOnWriteArrayList是一个适用于都多写少的线程安全集合类,但是也需要明白的是读写分离的集合都会存在弱一致性。说人话就是会有脏读的情况出现呀。
不过总体来说是一个不错的工具类,今天孤尽老师说到有人用这个类来当写多读少的用。
我其实想了一下为什么这哥们会这么玩。应该是有两种情况
1.真不明白原理
2.因为不会用的别方式实现线程安全的集合,然后该集合又正好实现了线程安全的写。所以就用了。。。。当万精油用咯
CopyOnWriteArrayList源码解读
先看下类图
很简单的常规操作,数据结构是数组。内部实现一个迭代器,然后搞一个COWSublist用来获取集合某一部分数据,在ArrayList里面sublist有的坑这里照样有。
我猜想sublist方法为什么不拷贝一份快照,而是直接用原来的集合然后做一个比较是否改变。
看了一下ArrayList的注释有下面这段话
-
This method eliminates the need for explicit range operations (of
- the sort that commonly exist for arrays). Any operation that expects
- a list can be used as a range operation by passing a subList view
- instead of a whole list. For example, the following idiom
- removes a range of elements from a list:
-
list.subList(from, to).clear();
所以我猜想了一下原因
1.本身该方法设计就是用来读取一个视图,视图参见数据库的视图。什么意思了?就不打算让你改呗
2.复制其实数据量大一点挺消耗内存的,节省资源提高性能?
咱们还是接着看代码,主要看读写分离的实现思路
private transient volatile Object[] array;//通过volatile保证可见性,但是其实也影响一点性能。因为每次都需要写回主内存之后,再从主内存强制加载到工作内存。
final Object[] getArray() {//获取数组
return array;
}
public E set(int index, E element) {
final ReentrantLock lock = this.lock;//有写操作加锁,保证只有一个线程修改
lock.lock();
try {
Object[] elements = getArray();//获取数组
E oldValue = get(elements, index);
if (oldValue != element) {//比较一下数据,不同就修改
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len);//拷贝一份数组出来
newElements[index] = element;//修改新数组的数据
setArray(newElements);//将新数组刷入到array,这时候由于array有volatile保证可见性。所以读线程能里面获取到最新的数据
} else {
// Not quite a no-op; ensures volatile write semantics
setArray(elements);//一样修改个啥嘛
}
return oldValue;//返回旧值
} finally {
lock.unlock();//释放锁
}
}
public E get(int index) {
return get(getArray(), index);//获取数据
}
好了上面的读写分离的实现就写完了,至于为啥适合多读少写。其实也很明白了
尼玛如果多写少读自己不会加一个锁实现?非要用这个?每次都拷贝一份数据,良心不会痛?
然后咱们再说说sublist的坑,继续看下源代码
public List<E> subList(int fromIndex, int toIndex) {
//常规操作哈,加一个锁保证数据不是变化的
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
if (fromIndex < 0 || toIndex > len || fromIndex > toIndex)
throw new IndexOutOfBoundsException();
return new COWSubList<E>(this, fromIndex, toIndex);//主要看这个里面
} finally {
lock.unlock();
}
}
// only call this holding l's lock将this给传进来了然后设置一下expectedArray
COWSubList(CopyOnWriteArrayList<E> list,
int fromIndex, int toIndex) {
l = list;
expectedArray = l.getArray();
offset = fromIndex;
size = toIndex - fromIndex;
}
//以后视图的任何操作都会调用这个方法判断一下,所以拿了视图之后不要随意修改原先list的数据不然会爆炸。
private void checkForComodification() {
if (l.getArray() != expectedArray)
throw new ConcurrentModificationException();
}
好了这个类的源码就写完了,其实JDK集合的代码真的简单。没有什么特别变态的操作,除了1.8的ConcurrentHashMap的部分源码看的有点难受~
欢迎扫码加入知识星球继续讨论