前言
java.util.ArrayList的线程安全变体,其中所有可变操作( add 、 set等)都是通过制作底层数组的新副本来实现的。 通过阅读源码可以一探究竟。
一、CopyOnWriteArrayList继承关系
二、数据结构
/** 用于修改的操作的锁 */
final transient ReentrantLock lock = new ReentrantLock();
/** 存放数据的数组 */
private transient volatile Object[] array;
常用操作的实现
初始化
/**
* Sets the array.
*/
final void setArray(Object[] a) {
array = a;
}
/**
* Creates an empty list.
*/
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
这里初始化一个长度为0的空数组
2.add操作
/**
* 添加一个元素到队尾
*
* @param e 被添加的元素
* @return {@code true}
*/
public boolean add(E e) {
// 操作前加锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
// 拷贝新数组,长度+1
Object[] newElements = Arrays.copyOf(elements, len + 1);
// 数组最后位置放入添加元素
newElements[len] = e;
// 替换原数组
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
通过加锁保证线程安全,通过复制新数组保证原数组访问的安全性
get操作
public E get(int index) {
return get(getArray(), index);
}
private E get(Object[] a, int index) {
return (E) a[index];
}
访问过程没有加锁,效率比较高,增删操作通过复制新数组方式来保证访问的线程安全
remove操作
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;
// 删除最后一个元素不需要移位,直接copy,不是最后一个元素分两段copy
if (numMoved == 0)
setArray(Arrays.copyOf(elements, len - 1));
else {
Object[] newElements = new Object[len - 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
setArray(newElements);
}
return oldValue;
} finally {
// 释放锁
lock.unlock();
}
}
删除操作和新增操作原理类似,都是通过加锁+复制新数组方式实现
set操作
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 {
// 确保volatile写语义,保证写操作对其他线程立即可见
setArray(elements);
}
return oldValue;
} finally {
lock.unlock();
}
}
set 比较用的是=,对非基础类型比较其实是引用地址
总结
CopyOnWriteArrayList通过加锁保证并发增改删的安全,通过复制数组保证访问的不加锁,提供访问效率,所以它适合于读多写少的场景