Java并发编程-CopyOnWriteArrayList原理
1.CopyOnWriteArrayList是一个ArrayList线程安全的变体,它的所有改变底层数组的方法如(add,set等)通过复制底层数组来实现。这原本很消耗性能,但当遍历操作远远多于修改操作时,可能会比其他方法更高效,当你不想同步遍历操作时这种实现可能会有用。你需要排除并发线程之间的冲突。快照形式的迭代方法在得迭代器创建时使用一个数组状态的引用。这个数组在迭代器的生命周期内绝不会改变,所以多线程之间的冲突是不可能发生的,迭代器保证不会抛出ConcurrentModificationException。自从迭代器创建以来,迭代器不会反映对该list的增加,删除,或者改变。迭代器不支持数组元素的操作,这些方法会抛出UnsupportedOperationException。和其他并发集合一样,一个将一个对象放入CopyOnWriteArrayList的线程happen-before于另一个线程中访问或者删除CopyOnWriteArrayList中的元素。
2.CopyOnWriteArrayList类关系图
由图可以看出CopyOnWriteArrayList实现了Serializable,RandomAccess,Cloneable,List,Collection和Iterable接口。所以CopyOnWriteArrayList支持随机访问,List和Collection接口的所有方法以及可以通过迭代器进行遍历操作。
3.CopyOnWriteArrayList核心字段
private static final long serialVersionUID = 8673264195747942595L;
/** 保护所有操作的锁 */
transient final ReentrantLock lock = new ReentrantLock();
/** 底层数据结构就是数组*/
private volatile transient Object[] array;
4.修改数组的相关方法实现
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);
} else {
// Not quite a no-op; ensures volatile write semantics(语义)
setArray(elements);
}
return oldValue;
} finally {
lock.unlock();
}
}
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();
}
}
public E remove(int index) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
E oldValue = get(elements, index);
// 计算需要移动的元素的个数
int numMoved = len - index - 1;
// 如果不需要移动则说明是删除的最后一个元素,直接将原数组从0到len-1拷贝一份
if (numMoved == 0)
setArray(Arrays.copyOf(elements, len - 1));
else {
// 创建一个比原数组长度小1的新数组
Object[] newElements = new Object[len - 1];
// 将原数组从0到index,index + 1到len的元素拷贝到新数组
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
// 更新数组引用
setArray(newElements);
}
return oldValue;
} finally {
lock.unlock();
}
}