CopyOnWriteArrayList是一个ArrayList线程安全的变体。当数组内容有所变化时,拷贝一份新的出来,在新对象上进行修改操作,完成后把新对象引用赋值给array属性。每发生一次改变,就需要复制一份数据,这样复制是需要一定开销的,所以CopyOnWriteArrayList适合读操作远大于修改操作的情况中。
CopyOnWriteArrayList构造函数:
public CopyOnWriteArrayList()
//Collection做初始化参数
public CopyOnWriteArrayList(Collection<? extends E> c)
//Array做初始化参数
public CopyOnWriteArrayList(E[] toCopyIn)
1.读操作
privateE get(Object[] a, intindex) {
return(E) a[index];
}
publicE get(intindex) {
returnget(getArray(), index);
}
直接读取,不需要加锁,因为即使读取过程中有其他线程改动List,也是开辟新的数组并在新数组上改动,旧数组对象始终是可用的。
2.写操作 在CopyOnWriteArrayList中写操作过程大致是这样的。在原有数组的基础上拷贝一份新的数组(容器副本),将改动操作在新数组上完成(即把新增元素加入新数组中),然后再把新数组对象的引用赋给CopyOnWriteArrayList的array。显然,在多线程环境中,为了保证线程安全,整个过程需要加锁。所以CopyOnWriteArrayList的写操作性能损耗是很大的,一方面需要竞争获取锁,另一方面需要进行复制操作。
下面以add(int index, E element)方法为例说明CopyOnWriteArrayList的修改操作:
//指定位置增加元素
public void add(int index, E element) {
final ReentrantLock lock = this.lock;
//修改array前获取锁
lock.lock();
try {
//获取原有array
Object[] elements = getArray();
int len = elements.length;
if (index > len || index < 0)
throw new IndexOutOfBoundsException("Index: "+index+
", Size: "+len);
Object[] newElements;
int numMoved = len - index;
if (numMoved == 0)
//index=length,即数组尾部新增一个元素,同add(E element)
newElements = Arrays.copyOf(elements, len + 1);
else {
//两次复制
newElements = new Object[len + 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index, newElements, index + 1,
numMoved);
}
//新增元素
newElements[index] = element;
//将新数组引用赋值给array
setArray(newElements);
} finally {
//释放锁
lock.unlock();
}
}
除了add方法,还有remove、removeRange、addIfAbsent等其他修改操作原理都是一样的,都是新new一个数组对象,在新array上进行修改操作,完事后再将新数组引用赋值给实例变量array,当然修改操作都是需要加锁的。
通过Iterator遍历CopyOnWriteArrayList的时候,不允许对array进行修改。remove、add、set方法直接抛出UnsupportedOperationException异常,这点是和普通list不同的地方。
我们可以看到,CopyOnWriteArrayList内部的array数组对象从被创建,到这个对象生命结束,是不可变的。变的是array变量的引用值,每做一次修改操作,array变量就指向新生成的数组对象,原对象被gc,如此反复。这种方式核心思想是减少锁竞争,从而提高高并发时的读取性能,但一定程度上牺牲了写的性能。 由此可得:“写入时复制(Copy-On-Write)”容器的线程安全性在于:只有正确地发布一个事实不可变的对象,那么在访问该对象时就不需要做同步操作。这也就解释了为什么通过迭代器Iterator是不允许进行修改操作的了。
timestamp_1470147855946_test.png
读操作无需加锁,并发环境性能不错,但只适用于读操作远大于写操作的场景。
CopyOnWriteArraySet完全基于CopyOnWriteArrayList实现。部分源码如下:
public class CopyOnWriteArraySet<E> extends AbstractSet<E>{
//内部维护了一个CopyOnWriteArrayList对象
private final CopyOnWriteArrayList<E> al;
public CopyOnWriteArraySet() {
al = new CopyOnWriteArrayList<E>();
}
public CopyOnWriteArraySet(Collection<? extends E> c) {
al = new CopyOnWriteArrayList<E>();
al.addAllAbsent(c);
}
//其所有操作都是代理给内部的CopyOnWriteArrayList对象执行。
public boolean add(E e) {
return al.addIfAbsent(e);
}
public boolean remove(Object o) {
return al.remove(o);
}
public Iterator<E> iterator() {
return al.iterator();
}
public Object[] toArray() {
return al.toArray();
}
。。。
}