CopyOnWriteArrayList源码 - wtstengshen/blog-page GitHub Wiki
####1,CopyOnWrite 是什么 CopyOnWrite,就是在写入/更新的时候,复制一份进行写入/更新操作。这样做的目的是不会影响读取的性能,同时读取不用加锁,但是写入和更新需要加锁处理,在一致性方面保证弱一致性。
CopyOnWriteArrayList的源码的核心在与一下两行代码:
/** The lock protecting all mutators */
transient final ReentrantLock lock = new ReentrantLock();
/** The array, accessed only via getArray/setArray. */
private volatile transient Object[] array;
其中ReentrantLock提供了在进行修改/插入/删除操作的加锁,volatile 类型的array,保证更新/删除/修改操作在内存中的可见性;
####2,分析CopyOnWriteArrayList的add操作
public boolean add(E e) {
// 注释(1)
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
// 注释(2)
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
// 注释(3)
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
代码分析如下: 1,首先add操作需要进行加锁,见注释(1)的代码,多线程环境只有获取锁成功才进行后续的操作,防止多个线程并发插入; 2,注释(2)的代码,对于add操作,首先把原有的数组进行Arrays的copy操作,拷贝成新的数组,然后add元素;这就是CopyOnWrite的操作,修改的时候先copy一份新的进行修改; 3,注释(3)的代码,把新copy完成的数组插入元素之后,调用setArray方法,其中setArray方法对volatile的array变量进行赋值操作,替换以前的
private volatile transient Object[] array;
数组内容,因为array是volatile修饰的,所以更改之后,对其他线程保证了内存的可见性;CopyOnWriteArrayList的修改操作通过加锁保证了并发插入的覆盖的问题,通过volatile变量保证了新copy的数组对内存的可见性;
####3,分析CopyOnWriteArrayList的get操作
public E get(int index) {
return get(getArray(), index);
}
final Object[] getArray() {
return array;
}
private E get(Object[] a, int index) {
return (E) a[index];
}
其实get操作的代码很简单,就是从array变量中获取index操作的元素,而且get操作有可能出现IndexOutOfBoundsException的异常; get操作没有进行任何加锁,其实也不需要进行加锁操作,volatile修饰的array变量保证了内存的可见性,但是在多线程环境下,有可能获取完get操作之后,是一个不存在的元素,因为getArray()方法其实获取的是旧的array的数组;所以,get操作是一个弱一致性的操作;
####4,分析CopyOnWriteArrayList的size方法
public int size() {
return getArray().length;
}
size方法更简单,直接获取array的length,所以这里获取的值也有能是不准确的;
CopyOnWriteArrayList的所有的查询操作,都是基于volatile 修饰的array的操作,查询的是当前的准确值,往后就有可能不准确的;但是在并发环境下,确实有可能线程A读取一个数据之后,线程B随后把他进行了删除或者修改操作,这样也是符合现实使用的情况的;同时因为CopyOnWriteArrayList是写的时候对副本进行copy操作,所有内存中有可能出现多个array副本的情况,类似与MVCC机制,如果并发更新/写入很多,会占用更多的内存,所以CopyOnWriteArrayList比较适用于读多,写少的环境,同时因为是基于副本对象进行遍历查询操作,所以在遍历并发修改的时候不会出现ConcurrentModificationException异常;