CopyOnWriteArrayList 是 ArrayList 的线程安全版本。用一句话归纳它的特色就是:全部的修改操做都是基于副原本进行的。java
在 java.util.concurrent
下,有不少线程安全的容器,大体能够分红三类 Concurrent*
、CopyOnWrite*
、Blocking*
这三类的容器均可以在并发环境下使用,可是实现的方式却不同。数组
Concurrent* 容器是基于无锁技术实现,性能很好,ConcurrentHashMap 就是典型表明;CopyOnWrie* 容器则是基于拷贝来实现的,因此对于内存有很大的开销,CopyOnWriteArrayList 就属于这一类;Blocking* 容器则使用 锁技术实现了阻塞技术,在某些场景下很是有用。安全
CopyOnWriteArrayList 的核心操做以下,就是经过不断的拷贝数组来更新容器:微信
CopyOnWriteArrayList 的成员变量以下:多线程
final transient Object lock = new Object();
private transient volatile Object[] array;
复制代码
变量的数量不多,仅仅包含一个锁对象和一个用来放元素数组。由于 CopyOnWriteArrayList 保证线程安全的方式很简单,不断的经过备份元素来保证数据不会被修改。并发
和其余线程安全的容器思路不同,这个容器从空间的角度来解决线程安全的问题。全部对容器的修改是基于副本进行的,修改的过程当中也经过锁对象锁来保证并发安全,从这个角度来讲,CopyOnWriteArrayList 的并发度也不会过高。因此一句话归纳就是使用 synchronized + Array.copyOf 来实现线程安全。函数
迭代器是基于副本进行的,即便原数组被改变,副本也不会被影响。也就不会抛出 ConcurrentModificationException 异常。可是这样也会让最新的修改没法及时体现出来。源码分析
get 方法直接读取数组就行,不须要上锁,多个线程同时读也就不会有并发的问题产生。post
public E get(int index) {
return elementAt(getArray(), index);
}
static <E> E elementAt(Object[] a, int index) {
return (E) a[index];
}
复制代码
下来来看一下 add 方法,代码很短:性能
// CopyOnWriteArrayList.add()
public boolean add(E e) {
synchronized (lock) {
Object[] es = getArray();
int len = es.length;
// 复制原数组,而且长度加一
es = Arrays.copyOf(es, len + 1);
es[len] = e;
// 指向新的数组
setArray(es);
return true;
}
}
复制代码
也就是是说,每次添加元素的时候,都会把原数组复制一次,并把复制后的数组长度加 1,而后把元素添加进数组,最后用新数组去替代旧数组,完成添加。这样 CopyOnWriteArrayList 根本就不须要扩容,由于每次添加元素都是一个扩容的过程。
// CopyOnWriteArrayList.remove()
public E remove(int index) {
synchronized (lock) {
Object[] es = getArray();
int len = es.length;
E oldValue = elementAt(es, index);
// 计算须要移动的元素
int numMoved = len - index - 1;
Object[] newElements;
// 若是删除的是最后一个元素,则不须要移动
if (numMoved == 0)
newElements = Arrays.copyOf(es, len - 1);
else {
newElements = new Object[len - 1];
// 删除的是中间元素,则须要分两次复制
System.arraycopy(es, 0, newElements, 0, index);
System.arraycopy(es, index + 1, newElements, index, numMoved);
}
// 指向新的数组
setArray(newElements);
return oldValue;
}
}
复制代码
删除元素的状况就要复杂一些。删除的时候若是是删除中间的元素,须要后面元素进行移动。而后新数组的长度也会减 1,这就至关于缩容过程。
CopyOnWriteArrayList 的迭代器的实现也很不复杂:
# COWIterator 构造函数
COWIterator(Object[] es, int initialCursor) {
cursor = initialCursor;
// 容器元素的副本
snapshot = es;
}
复制代码
能够看到,构造迭代器的时候,直接把整个元素的副本都传进来了,后续的操做都会在这个副本上进行,甚至都须要上锁。因此是 fail-safe 的。
在 CopyOnWriteArrayList 中,有两种数组拷贝方式 Arrays.copyOf
和 System.arraycopy
。这两种方式有什么区别吗?其实是没有的,来看一下 Arrays.copyOf 的源码:
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
复制代码
没错,Arrays.copyOf 调用了 System.arraycopy 来实现数组拷贝。
经过上面的分析可知,CopyOnWriteArrayList 的读效率很高,可是写的效率很低,因此比较适合读多写少的场景。
另外须要说一句,CopyOnWriteArraySet 使用 CopyOnWriteArrayList 实现。Set 一如继往喜欢使用现成的类来实现。
相关文章
关注微信公众号,聊点其余的