CopyOnWriteArrayList是一个线程安全的集合类,它的底层也是数组,和ArrayList是一样的,但是不同是ArrayList是线程不安全,不能用于多线程中,而CopyOnWriteArrayList是线程安全的。
CopyOnWriteArrayList的属性
/** The lock protecting all mutators */
// 从这个可以看出CopyOnWriteArrayList的线程安全是通过ReentrantLock加锁来实现的
final transient ReentrantLock lock = new ReentrantLock();
/** The array, accessed only via getArray/setArray. */
// array这个属性,就是底层的数组,这个属性加上了volatile关键字,保证了array的可见性,和有序性
private transient volatile Object[] array;
CopyOnWriteArrayList的add()方法,添加方法大致是先复制一个新数组,将元素添加到新数组中,然后再用新数组替代旧数组。
/**
* 添加元素
*/
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(); // 解锁,在finally中,保证一定释放锁
}
}
CopyOnWriteArrayList的get()方法,阅读源码发现,读这个操作是不需要加锁的,直接读取array数组中的数据。(get(int index)源码并没有判断下标是否会越界)
/**
* 读取数据操作
*/
public E get(int index) {
// getArray()是得到array这个数组,直接是 array[index]
return get(getArray(), index);
}
private E get(Object[] a, int index) {
return (E) a[index];
}
CopyOnWriteArrayList的remove()方法,删除方法是,创建一个新数组,然后将旧数组数组复制到新数组中,复制的时候,不复制要删除的元素。因此复制的时候会判断删除的元素是否是最后一个元素,如果是复制的时候直接不复制就行,如果不是,则需要很两段进行复制。(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;
if (numMoved == 0) // 判断是否是删除最后一个元素
setArray(Arrays.copyOf(elements, len - 1)); // 直接缩短,去掉了最后一个元素
else { // 不是删除最后一个元素
Object[] newElements = new Object[len - 1]; // 新数组
// System.arraycopy()是一个native方法,这个方法是用来复制数组的数据的,
// arraycopy(被复制的数组,开始复制的下标,复制到哪个数组,从这个数组的哪个下标开始粘贴,复制的长度)
// 以index为分界线,分两段开始复制
System.arraycopy(elements, 0, newElements, 0, index); //复制前半段
System.arraycopy(elements, index + 1, newElements, index,
numMoved); // 复制后半段
setArray(newElements); // 新数组替代旧数组
}
return oldValue; // 返回被删除的值
} finally {
lock.unlock(); // 解锁
}
}
总结
CopyOnWriteArrayList在操作元素的时候,读写是分离开的,写的时候会创建一个新的数组,将旧数组元素复制到新数组中,然后将要添加的元素添加到新数组,之后新数组替代旧数组。而读的时候,直接根据下标读取元素,不会被写操作给阻塞。删除操作和写操作类似,也是创建一个新数组,但是删除需要判断,如果是删除最后一个元素,就直接复制元素,不复制最后一个就行,但是大多情况下,删除的不是最后一个元素,需要以index很分割线,分为两段进行复制到新数组中。
写操作是加锁的,读操作是不加锁的,这样读写分离可以有效的提高效率,但是这样会有一个数据一致性的问题,就是在并发情况下,一个线程,写操作添加数据,一个读操作,读取数据,可能读操作读取了旧数组的数组,导致数据不一致。(虽然array属性加入volatile关键字,但是这个写操作是先在新数组操作,然后复制到旧数组,新数组是没法对其他线程可见的。)