CopyOnWriteArrayList读取时不加锁只是写入和删除时加锁,所以一个线程X读取的时候另一个线程Y可能执行remove操作。remove操作首先要获取独占锁,然后进行写时复制操作,就是复制一份当前的array数组,然后在复制的新数组里面删除线程X通过get访问的元素,比如:1。删除完成后让array指向这个新的数组。
在线程x执行get操作的时候并不是直接通过全局array访问数组元素而是通过方法的形参a访问的,a指向的地址和array指向的地址在调用get方法的那一刻是一样的,都指向了堆内存的数组对象。之后改变array指向的地址并不影响get的访问,因为在调用get方法的那一刻形参a指向的内存地址就已经确定了,不会改变。
public E get(int index) {
return get(getArray(), index);
}
private E get(Object[] a, int index) {
//形参直接指向了原数组,array引用地址的改变并不影响这里取值
return (E) a[index];
}
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.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
//将array指向新数组
setArray(newElements);
}
return oldValue;
} finally {
lock.unlock();
}
}
image.png
删除完成后让array指向新的数组。这个时候{1,2,3}这个数组的引用计数不为0而是为1,线程x还在使用它。
image.png
所以,虽然线程y已经删除了index处的元素但是线程x的获取操作还是会返回index处的元素。
网友评论