CopyOnWriteArrayList 源码学习
特性
- CopyOnWriteArrayList 是 ArrayList的并发优化版本,采用了CopyOnWrite策略,在修改时先复制一个快照来修改,改完再让内部指针指向新数组。
- 因为对快照的修改对读操作来说不可见,所以只有写锁没有读锁,加上复制的昂贵成本,典型的适合读多写少的场景。
- CopyOnWriteArrayList 实现了List、RandomAccess、Cloneable以及Serializable接口
- CopyOnWriteArrayList 线程安全(List下另一个线程安全的类是Vector)
- CopyOnWriteArrayList 利用ReentrantLock锁+ 数组拷贝 + volatile关键字保证了线程安全(jdk1.8)。
PS:jdk11用的synchronized锁
几个重要方法
get() & add()
final transient ReentrantLock lock = new ReentrantLock();
// 数组加了volatile 关键字保证可见性
private transient volatile Object[] array;
// 读不加锁
public E get(int index) {
return get(getArray(), index);
}
// 写加锁
public boolean add(E e) {
final ReentrantLock lock = this.lock;
// 加锁
lock.lock();
try {
// 取原数组
Object[] elements = getArray();
int len = elements.length;
// cow操作
Object[] newElements = Arrays.copyOf(elements, len + 1);
// 将新元素添加到末尾
newElements[len] = e;
// 写完立马用新数组替换原数组
setArray(newElements);
return true;
} finally {
// 最后解锁
lock.unlock();
}
}
这里可以了解下 jdk11 中add()方法的实现
// jdk11
public boolean add(E e) {
synchronized (lock) {
Object[] es = getArray();
int len = es.length;
es = Arrays.copyOf(es, len + 1);
es[len] = e;
setArray(es);
return true;
}
}
getArray()
// 取原数组
inal Object[] getArray() {
return array;
}
setArray()
// 用新数组替换原数组
final void setArray(Object[] a) {
array = a;
}
indexOf()
功能:检索元素在数组中的下标,需遍历整个数组,效率低
private static int indexOf(Object o, Object[] elements,
int index, int fence) {
if (o == null) {
for (int i = index; i < fence; i++)
if (elements[i] == null)
return i;
} else {
for (int i = index; i < fence; i++)
if (o.equals(elements[i]))
return i;
}
return -1;
}
PS:此方法只能在内部方法中调用(private)
contains()
功能:判断数组中是否包含了某个元素,内部调用了indexOf()方法
public boolean contains(Object o) {
Object[] elements = getArray();
return indexOf(o, elements, 0, elements.length) >= 0;
}
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;
// 若元素是在数组末尾,直接替换
if (numMoved == 0)
setArray(Arrays.copyOf(elements, len - 1));
else {
Object[] newElements = new Object[len - 1];
// 这里调用了System类下的方法,属于native方法
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index + 1, newElements, index,numMoved);
setArray(newElements);
}
return oldValue;
} finally {
lock.unlock();
}
}
解释
- lock锁:保证同一时刻只能有一个线程操作数组
- volatile:保证了一旦数组的内存地址发生了变化(写时发生),其他线程立马知道数组已经被修改
- 一些批量操作(比如
addAll()
,clear()
)满足原子性,也就是说不存在指令重排导致的未赋值就访问等情况。
优缺点
优
- 读不加锁,效率高
- 可以在多线程下操作list
缺:
- 读到的数组可能不是最新值
- 每次写需要创建一个新数组,占用额外内存