介绍
java并发包中的并发List只有CopyOnWriteArrayList,CopyOnWriteArrayList的修改操作底层都是通过拷贝一份数组进行的。使用ReentrantLock独占锁来保证多线程并发下只有一个线程进行修改操作,下面我们通过源码分析来逐步了解。
初始化方法
/**
* Creates an empty list.
*/
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
无参构造器中,创建了一个长度为1Object数组.
添加元素
从上图可知CopyOnWriteArrayList的添加元素方法有7个,我们着重讲下add(E) 这个方法,其他方法的实现原理大同小异。
/**
* Appends the specified element to the end of this list.
* 翻译:在列表的最后添加元素
* @param e element to be appended to this list
* 翻译:添加到列表的元素
* @return {@code true} (as specified by {@link Collection#add})
*/
public boolean add(E e) {
//1. 获取重入锁(独占锁)
final ReentrantLock lock = this.lock;
lock.lock();
try {
//2. 获取array
Object[] elements = getArray();
int len = elements.length;
//3. 复制原array到新array,并添加元素
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
//4. 将新array替换原array
setArray(newElements);
return true;
} finally {
//5. 释放重入锁
lock.unlock();
}
}
从1 和 5 可以看出 的方法,通过独占锁的机制,保证多线程并发修改元素时,只能有一个线程进行,其他线程会阻塞挂起,从而确保了此方法的原子性。
2 、3 处 通过复制原array到新array中,通过对新array进行添加元素操作,此处可知CopyOnWriteArrayList是一个无界的队列。4 处 将 新数组替换了原数组
删除元素
CopyOnWriteArrayList 有6个删除方法,我们着重讲解下remove(int)这个方法。
/**
* Removes the element at the specified position in this list.
* Shifts any subsequent elements to the left
* Returns the element that was removed from the list.
* 翻译:删除列表中指定位置的元素,往左边移动随后的元素,返回从列表中删除的元素。
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E remove(int index) {
//1. 获取锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
//2. 获取原array
Object[] elements = getArray();
//3. 获取要删除的元素
int len = elements.length;
E oldValue = get(elements, index);
int numMoved = len - index - 1;
if (numMoved == 0)
setArray(Arrays.copyOf(elements, len - 1));
else {
//4. 通过两次复制,来删除元素并整合新的array
Object[] newElements = new Object[len - 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
//5. 新array替换原array
setArray(newElements);
}
return oldValue;
} finally {
lock.unlock();
}
}
获取指定位置元素
@SuppressWarnings("unchecked")
private E get(Object[] a, int index) {
return (E) a[index];
}
/**
* {@inheritDoc}
*
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
return get(getArray(), index);
}
在如上代码中,当线程threadA 执行get(int index ),是分两个步骤:1. 获取数组 2. 通过数组下标index索引获取指定元素,它会存在数据弱一致性的问题。当线程threadB 在 线程 threadA执行步骤1后和步骤2之前时,删除了threadA 要访问的元素(假设为x元素),那么threadA执行步骤2时获取的是x元素,而不是其他的。因为threadA在执行步骤1后,操作的是原数组(局部变量)。
总结
CopyOnWriteArrayList通过写时复制来保证数据的一致性,而它获取、修改、写入这三个操作并不是原子性,所以通过独占锁来保证任何一时刻只能有一个线程对数据进行操作。另外CopyOnWriteArraySet的实现原理和CopyOnWriteArrayList也是类似的。