前言
CopyOnWriteArrayList这是一个ArrayList的线程安全的变体,其原理大概可以通俗的理解为:初始化的时候只有一个容器,很常一段时间,这个容器数据、数量等没有发生变化的时候,大家(多个线程),都是读取(假设这段时间里只发生读取的操作)同一个容器中的数据,所以这样大家读到的数据都是唯一、一致、安全的,但是后来有人往里面增加了一个数据,这个时候CopyOnWriteArrayList 底层实现添加的原理是先copy出一个容器(可以简称副本),再往新的容器里添加这个新的数据,最后把新的容器的引用地址赋值给了之前那个旧的的容器地址,但是在添加这个数据的期间,其他线程如果要去读取数据,仍然是读取到旧的容器里的数据。
原理
CopyOnWriteArrayList是一个线程安全的ArrayList
如果一段并发程序,读操作明显多于写操作的话,那么使用CopyOnWriteArrayList的性能会比Vector更高
CopyOnWriteArrayList的实现原理就是读写分离,它对所有的写操作都使用ReentrantLock来加锁,对所有的读操作都不加锁,那它是怎么保证线程安全性问题的呢?
CopyOnWriteArrayList在写操作的时候,都会将list中的数组copy一份作为缓存,然后对该缓存中的数组进行操作(此时若有其他线程过来读的话,那么该线程读的还是原先没有被修改过的数组,若有其他线程过来写的话,那么该线程会因为ReentrantLock的原因被锁在外面。),操作完毕后再将list中的数组地址引用指向修改后的新数组地址。
由CopyOnWriteArrayList的原理我们可以看出,我们每次往list里面写数据的时候,数组都需要重新copy一份,所以CopyOnWriteArrayList不需要实现像ArrayList一样的扩容机制,初始创建时让list中的数组长度为0,我们每次add元素的时候,只需要对新数组长度进行加1操作即可,所以CopyOnWriteArrayList实现起来相对还是比ArrayList简单的。
添加的逻辑很简单,先将原容器copy一份,然后在新副本上执行写操作,之后再切换引用。当然此过程是要加锁的。
public boolean add(E e) {
//ReentrantLock加锁,保证线程安全
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;
if (numMoved == 0)
//如果要删除的是列表末端数据,拷贝前len-1个数据到新副本上,再切换引用
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();
}
}
读取操作没有任何同步控制和锁操作,理由就是内部数组 array 不会发生修改,只会被另外一个 array 替换,因此可以保证数据安全。
/** The array, accessed only via getArray/setArray. */
private transient volatile Object[] array;
public E get(int index) {
return get(getArray(), index);
}
@SuppressWarnings("unchecked")
private E get(Object[] a, int index) {
return (E) a[index];
}
final Object[] getArray() {
return array;
}