[TOC]java
咱们都很熟悉容器对象ArrayList,而且在初学时就被告知ArrayList不是线程安全的:当咱们在使用迭代器遍历ArrayList时,若是有其余线程修改了ArrayList对象,那么就会抛出ConcurrentModificationException异常。相较于Vector使用synchronized加锁保证线程安全性,JUC提供了多线程版“ArrayList”:CopyOnWriteArrayList。下面是JDK对CopyOnWriteArrayList的介绍:数组
A thread-safe variant of ArrayList in which all mutative operations (add, set, and so on) are implemented by making a fresh copy of the underlying array. This is ordinarily too costly, but may be more efficient than alternatives when traversal operations vastly outnumber mutations, and is useful when you cannot or don't want to synchronize traversals, yet need to preclude interference among concurrent threads. The "snapshot" style iterator method uses a reference to the state of the array at the point that the iterator was created. This array never changes during the lifetime of the iterator, so interference is impossible and the iterator is guaranteed not to throw ConcurrentModificationException. The iterator will not reflect additions, removals, or changes to the list since the iterator was created. Element-changing operations on iterators themselves (remove, set, and add) are not supported. These methods throw UnsupportedOperationException.安全
大意是CopyOnWriteArrayList是线程安全版本的ArrayList,全部对CopyOnWriteArrayList修改的操做都是在内部数组的拷贝上进行操做的,这样作虽然内存花费大,可是在遍历操做大于修改操做时这样效率更高,能够有效防止抛出ConcurrentModificationException异常,迭代器迭代期间不支持对元素更改操做,不然会抛出UnsupportedOperationException异常。数据结构
CopyOnWriteArrayList实现了List接口,List表示是有序的Collection,即它用某种特定的插入顺序来维护元素顺序;实现了标记接口RandomAccess接口支持快速访问;实现了Iterable接口可使用迭代器遍历容器元素。多线程
CopyOnWriteArrayList互斥锁用于对修改容器元素阶段加锁,被volatile修饰的Object数组是CopyOnWriteArrayList存储数据的底层数据结构,经过volatile保证可以读到其余线程对CopyOnWriteArrayList数据的修改,对于数组的访问都是经过getArray/setArray方法。 CopyOnWriteArrayList提供了三个重载的构造函数,无参构造函数会调用setArray方法构造一个空的Object数组,另外两个构造函数分别传入集合/数组参数,将集合/数组内元素存入CopyOnWriteArrayList的底层Object数组。dom
public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { private static final long serialVersionUID = 8673264195747942595L; //互斥锁 final transient ReentrantLock lock = new ReentrantLock(); //底层存储数据数组,只能经过getArray/setArray访问设置,volatile动态数组 private transient volatile Object[] array; final Object[] getArray() { return array; } final void setArray(Object[] a) { array = a; } public CopyOnWriteArrayList() { setArray(new Object[0]); } //传入Collection集合对象,将集合中元素存入CopyOnWriteArrayList public CopyOnWriteArrayList(Collection<? extends E> c) { Object[] elements; if (c.getClass() == CopyOnWriteArrayList.class) elements = ((CopyOnWriteArrayList<?>)c).getArray(); else { elements = c.toArray(); // c.toArray might (incorrectly) not return Object[] (see 6260652) if (elements.getClass() != Object[].class) elements = Arrays.copyOf(elements, elements.length, Object[].class); } setArray(elements); } //传入数组 public CopyOnWriteArrayList(E[] toCopyIn) { setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class)); } }
add方法的做用是把传入元素添加到链表list的末尾。add方法有两点须要注意:1.在写入过程使用了互斥锁,因此同一时间只有一个线程在修改CopyOnWriteArrayList 2.增长元素并非直接在原数组操做,而是在原数组的拷贝数组上添加元素的,添加完成后再调用setArray方法用新数组代替原始数组函数
public boolean add(E e) { //得到互斥锁 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(); } }
这个方法的做用是把新元素插入到特定位置,会把原来位置的元素向后挤。过程与上面的add大体相同。性能
public void add(int index, E element) { //互斥锁 final ReentrantLock lock = this.lock; lock.lock(); try { //原始数组 Object[] elements = getArray(); int len = elements.length; //检查index有效性 if (index > len || index < 0) throw new IndexOutOfBoundsException("Index: "+index+ ", Size: "+len); //拷贝数组 Object[] newElements; //从index到数组末尾要向后移动一位数组元素的个数 int numMoved = len - index; //若是index==length,直接把原数组复制到新数组 if (numMoved == 0) newElements = Arrays.copyOf(elements, len + 1); //不然分红两段复制,原始数组index前面的元素位置一一对应赋值到新数组,原数组index开始的元素复制到 //新数组index+1到length+1,至关于依次后移。空出来的index就是新元素插入的位置 else { newElements = new Object[len + 1]; System.arraycopy(elements, 0, newElements, 0, index); System.arraycopy(elements, index, newElements, index + 1, numMoved); } //插入新元素 newElements[index] = element; setArray(newElements); } finally { lock.unlock(); } }
获取索引位置为index位置处的元素,获取元素过程当中没有使用互斥锁上锁。优化
public E get(int index) { return get(getArray(), index); } @SuppressWarnings("unchecked") private E get(Object[] a, int index) { //返回数组index处位置 return (E) a[index]; }
移除index处元素。因为涉及到修改到对链表内元素的修改,所以移除过程会使用互斥锁上锁。this
public E remove(int index) { //上锁 final ReentrantLock lock = this.lock; lock.lock(); try { //原始数组 Object[] elements = getArray(); int len = elements.length; //数组index处要移除的元素 E oldValue = get(elements, index); //index+1到数组末尾要移动的元素个数 int numMoved = len - index - 1; //若是要移除的元素在数组末尾(index=len-1),直接复制数组区间[0,len-2]全部元素到新数组 if (numMoved == 0) setArray(Arrays.copyOf(elements, len - 1)); //若是移除的元素再也不末尾,分红两段赋值,首先把[0,index-1]区间元素复制到新数组,再把 //[index+1,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(); } }
CopyOnWriteArrayList支持使用迭代器迭代,使用iterator方法返回COWIterator对象,在迭代过程当中没有上锁,也不支持remove/set/add等修改方法。
//返回COWIterator对象 public Iterator<E> iterator() { return new COWIterator<E>(getArray(), 0); } //实现迭代器的内部类 static final class COWIterator<E> implements ListIterator<E> { //遍历时原始数组的快照 private final Object[] snapshot; //迭代器迭代的游标 private int cursor; private COWIterator(Object[] elements, int initialCursor) { cursor = initialCursor; snapshot = elements; } public boolean hasNext() { return cursor < snapshot.length; } public boolean hasPrevious() { return cursor > 0; } @SuppressWarnings("unchecked") public E next() { if (! hasNext()) throw new NoSuchElementException(); return (E) snapshot[cursor++]; } @SuppressWarnings("unchecked") public E previous() { if (! hasPrevious()) throw new NoSuchElementException(); return (E) snapshot[--cursor]; } public int nextIndex() { return cursor; } }
Copy-On-Write简称COW,中文简称写入时复制,是一种程序设计优化策略,具体思想就是对于共享内容作修改操做时,会把共享内容复制出来,在复制内容上修改,修改完成后在返还到原内容上。对于CopyOnWriteArrayList而言,向容器添加元素是先把容器复制一份,向复制的容器添加元素,添加成功后把复制的容器赋值给原容器对象;而对于读取容器的操做直接在原容器进行操做。CopyOnWriteArrayList利用了COW技术实现读写的分离,对于写操做实行加锁保证安全性,读操做不改变容器不需加锁,相对于Vector对全部操做加锁来保证安全性的效率更高,适合于读多写少的场景。同时在CopyOnWriteArrayList保存数据量较大时,对于容器的写入因为复制原容器产生新容器用于写操做,形成了两倍的内存消耗,会引起频繁的垃圾回收,下降性能。