CopyOnWriteArrayList简介
CopyOnWriteArrayList是ArrayList的并发容器。
写操作:
通过名字就可以看出来其原理。CopyOnWriteArrayList会将对象数组拷贝到新数组中,当写的时候会写在新数组上,然后将CopyOnWriteArrayList的对象数组替换成新数组。
读操作:
读操作不会加任何锁
了解了写操作和读操作的做法后,我们发现CopyOnWriteArrayList适合于写少读多的场景。
CopyOnWriteArrayList源码解析
主要属性
//独占锁,当发生写操作时加的锁
final transient ReentrantLock lock = new ReentrantLock();
//数组
private transient volatile Object[] array;
get(int index)
public E get(int index) {
return get(getArray(), index);
}
private E get(Object[] a, int index) {
return (E) a[index];
}
get()没什么说的,常规操作!
add(E e)
public boolean add(E e) {
final ReentrantLock lock = this.lock;
//加锁
lock.lock();
try {
//获取array
Object[] elements = getArray();
int len = elements.length;
//将array 拷贝到newElements
Object[] newElements = Arrays.copyOf(elements, len + 1);
//将e加入到newElements的最后位置
newElements[len] = e;
//将array引用设置为newElements
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
当add(e)的时候,有以下步骤:
1、将array拷贝到一个newElements中,newElements的长度是len+1
2、将e加入到newElements的最后位置中
3、将array引用设置为newElements
从源码我们可以看出,我们可以看出一个问题:
当线程调用add()的时候,这个时候如果有其他线程来读数据,会读不到新加入的数据,会出现短暂的读写不一致!
set(int index,E e)
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 {
setArray(elements);
}
return oldValue;
} finally {
lock.unlock();
}
}
remove(int 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;
//如果为0,说明删除的是最后一个元素
if (numMoved == 0)
setArray(Arrays.copyOf(elements, len - 1));
else {
Object[] newElements = new Object[len - 1];
//将从elements的下标0复制index个元素到newElements的0下标开始的index个元素
System.arraycopy(elements, 0, newElements, 0, index);
//将从elements的下标index+1复制numMoved个元素到newElements的index下标开始的numMoved个元素
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
setArray(newElements);
}
return oldValue;
} finally {
lock.unlock();
}
}
总结
写操作的其他方法我们也不说了,都是这个套路,加个lock,然后拷贝一个新数组,将写操作写到新数组中,然后将新数组替换原来的数组。
读操作不加锁,读取的是原本的数组,有的时候会存在短暂的读写不一致!