介绍
并发包中的并发List只有CopyOnWriteArrayList。CopyOnWriteArrayList是一个线程安全的ArrayList,对其进行的修改操作都是在底层的一个复制的数组(快照)上。
每个CopyOnWriteArrayList对象里面有一个array数组对象用来存放具体元素,ReentrantLock独占锁对象用来保证同时只有一个线程对array进行修改。ReentrantLock是独占锁。
主要方法源码解析
初始化
无参CopyOnWriteArrayList(),入参为toCopyIn:CopyOnWriteArrayList(E[] toCopyIn),入参为集合:CopyOnWriteArrayList(Collections<? extends E> c)。
添加元素
有add(E e)、add(int index, E element)、addifAbsent(E e)和addAllAbsent(Collection<? extends E> c)等。原理类似,以下讲解add(E e)。
public boolean add(E e) {
//获取独占锁(1)
final ReentrantLock lock = this.lock;
lock.lock() ;
try {
//(2)获取array
Object[] elements= getArray() ;
//(3)复制array到新数组,添加元素到新数组
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1) ;
newElements[len] = e ;
//(4)使用新数组替换添加前的数组
setArray(newElements) ;
return true;
} finally {
//(5)释放独占锁
lock.unlock();
}
}
获取指定位置元素
public E get(int index) {
return get (getArray() , index );
}
final Object[] getArray() {
return array;
}
private E get(Object[] a , int index) {
return (E) a[index];
}
分为两步,先获取数组array,再通过下标index访问元素,此过程没有加锁同步。若另一个线程在本线程获取数组array之后和访问元素之前改变了数组,则出现了写时复制策略产生的弱一致性问题。
修改指定元素
public E set(int index, E element) {
//获取独占锁
final ReentrantLock lock = this.lock;
lock.lock() ;
try {
//获取array 和 旧值
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 {
//若相等,为了保证volatile语义,仍需重新设置array
setArray(elements) ;
}
return oldValue; }
}finally {
//释放独占锁
lock.unlock();
}
}
删除元素
使用 E remove(int index)、boolean remove(Object o)和 boolean remove(Object o, Object[] snapshot, int index)等方法,它们的原理一样。下面讲解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 - l ;
//若删除的是最后一个元素
if(numMoved == 0)
setArray(Arrays.copyOf(elements, len - l)) ;
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();
}
}
弱一致性的迭代器
所谓弱一致性是指返回迭代器后,其他线程对 list 的增删改对迭代器是不可见的。
总结
CopyOnWriteArrayList 使用写时复制的策略来保证 list 的一致性,而获取一修改一写入三步操作并不是原子性的,所以在增删改的过程中都使用了独占锁,来保证在某个时间只有一个线程能对 list 数组进行修改。另外 CopyOnWriteArrayList 提供了弱一致性的迭代器,从而保证在获取迭代器后,其他线程对 list 的修改是不可见的,迭代器遍历的数组是一个快照。 另外,CopyOnWriteArraySet 的底层就是使用它实现的。