CopyOnWriteArrayList是ArrayList的一个线程安全的变体,其中全部可变操做(add、set 等等)都是经过对底层数组进行一次新的复制来实现的。 这通常须要很大的开销,可是当遍历操做的数量大大超过可变操做的数量时,这种方法可能比其余替代方法更 有效。在不能或不想进行同步遍历,但又须要从并发线程中排除冲突时,它也颇有用。“快照”风格的迭代器方法在建立迭代器时使用了对数组状态的引用。此数组在迭代器的生存期内不会更改,所以不可能发生冲突,而且迭代器保证不会抛出 ConcurrentModificationException。建立迭代器之后,迭代器就不会反映列表的添加、移除或者更改。在迭代器上进行的元素更改操做(remove、set 和 add)不受支持。这些方法将抛出 UnsupportedOperationException。 容许使用全部元素,包括 null。java
内存一致性效果:当存在其余并发 collection 时,将对象放入 CopyOnWriteArrayList 以前的线程中的操做 happen-before 随后经过另外一线程从 CopyOnWriteArrayList 中访问或移除该元素的操做。数组
根据API对CopyOnWriteArrayList的介绍,其原理以及使用场景已经比较清晰了,下面咱们经过源码来分析下。安全
实现原理并发
API已经说的比较清楚了,因为数组的特殊结构,因此若是想要对数据进行结构性修改,如增长一个元素,删除一个元素,都是很麻烦的,因此没法将对一个数组的结构性修改缩小到一个原子指令范围,不像链表能够经过CAS修改next指针来修改链表。因此CopyOnWriteArrayList经过将任何对底层数组进行结构性修改的操做变成针对一个新的副本的修改,而后用修改后的副原本替换原来的数组,来实现遍历与修改分离,以保证数组高效的访问效率。app
经常使用方法解读ui
CopyOnWriteArrayList的重要的几个方法:add(int, E)/add(E)/set(int, E)/remove(int)/iterator(),其中前四个是对CopyOnWriteArrayList的结构进行修改,最后一个是对CopyOnWriteArrayList进行遍历。下面针对源码逐一进行分析。this
add(int, E)和add(E)线程
/** * Inserts the specified element at the specified position in this list. * Shifts the element currently at that position (if any) and any subsequent * elements to the right (adds one to their indices). * * @throws IndexOutOfBoundsException * {@inheritDoc} */ public void add(int index, E element) { // 加锁 final ReentrantLock lock = this.lock; lock.lock(); try { // 读取底层数组对象 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) // ==0表示插入的位置是数组的最后一个位置,因此该位置前面的元素原样不动复制到新的数组便可 // 这里经过复制elements数组生成一个新的数组,注意这里新的数组长度是原数组+1,因此新数组的最后一个元素是NULL newElements = Arrays.copyOf(elements, len + 1); else { // 将原数组的0~index-1原样复制到新的数组中, // 而index以后的元素对应复制到新数组的index+1以后,即中间空出一个位置用于放置带插入元素 newElements = new Object[len + 1]; System.arraycopy(elements, 0, newElements, 0, index); System.arraycopy(elements, index, newElements, index + 1, numMoved); } // 将element插入到新的数组 newElements[index] = element; // 将更新底层数组的引用,因为array是volatile的,因此对其的修改可以当即被后续线程可见 setArray(newElements); } finally { // 释放锁 lock.unlock(); } } /** * Appends the specified element to the end of this list. * * @param e * element to be appended to this list * @return <tt>true</tt> (as specified by {@link Collection#add}) */ // @By Vicky:该方法至关于调用add(array.length, e) 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()方法的实现很简单,经过加锁保证线程安全,经过Arrays.copyOf根据原数组复制一个新的数组,将要插入的元素插入到新的数组的对应位置,而后将新的数组赋值给array,经过volatile保证内存可见。指针
set(int, E)code
/** * Replaces the element at the specified position in this list with the * specified element. * * @throws IndexOutOfBoundsException * {@inheritDoc} */ // @By Vicky:更新指定位置元素 public E set(int index, E element) { // 加锁 final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); // 获取需更新的元素 Object oldValue = elements[index]; // // // 需更新的值不等于原值(注意此处的不等是==,不是equals(),即oldValue和element必须是引用同一个对象才可) if (oldValue != element) { int len = elements.length; // 复制一个新的数组,并将index更新成新的值,更新引用 Object[] newElements = Arrays.copyOf(elements, len); newElements[index] = element; setArray(newElements); } else { // Not quite a no-op; ensures volatile write semantics // 此处因为更新的值与原值是同一个对象,因此其实可不更新引用 // 从注释能够看出更新的目的是出于写volatile变量 setArray(elements); } return (E) oldValue; } finally { // 释放锁 lock.unlock(); } }
set()比add()更新简单,只须要复制一个新的数组,而后更新新的数组的指定位置的元素,而后更新引用便可。
remove(int)
/** * Removes the element at the specified position in this list. * Shifts any subsequent elements to the left (subtracts one from their * indices). Returns the element that was removed from the list. * * @throws IndexOutOfBoundsException {@inheritDoc} */ // @By Vicky:删除指定位置的元素 public E remove(int index) { // 加锁 final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; Object oldValue = elements[index]; int numMoved = len - index - 1;// 须要移动的元素的个数 if (numMoved == 0) // ==0表示删除的位置是数组的最后一个元素,只须要简单的复制原数组的len-1个元素到新数组便可 setArray(Arrays.copyOf(elements, len - 1)); else { // 将原数组的0-index-1复制到新数组的对应位置 // 将原数组的index+1以后的元素复制到新数组,丢弃原数组的index位置的元素 Object[] newElements = new Object[len - 1]; System.arraycopy(elements, 0, newElements, 0, index); System.arraycopy(elements, index + 1, newElements, index, numMoved); setArray(newElements); } return (E) oldValue; } finally { lock.unlock(); } } // @By Vicky:删除指定元素,而非指定位置的元素 public boolean remove(Object o) { // 加锁 final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; if (len != 0) { // Copy while searching for element to remove // This wins in the normal case of element being present int newlen = len - 1;// 删除以后数组的长度 Object[] newElements = new Object[newlen];// 建立新的数组 for (int i = 0; i < newlen; ++i) {// 从0-len-1遍历原数组 if (eq(o, elements[i])) {// 若是是待删除元素,则将该元素以后的元素复制到新数组中 // found one; copy remaining and exit for (int k = i + 1; k < len; ++k) newElements[k - 1] = elements[k]; // 设置新数组 setArray(newElements); return true; } else // 将该元素插入到新数组 newElements[i] = elements[i]; } // 确认最后原数组一个元素是否与待删除元素相等,是的话直接将修改引用便可,由于前面已经为新数组赋完值了 // special handling for last cell if (eq(o, elements[newlen])) { setArray(newElements); return true; } } // 到这里说明数组中没有与待删除元素相等的元素,因此直接返回false, // 可是这里并无写volatile变量,看来set那里也只是写着好玩 return false; } finally { lock.unlock(); } }
remove()有两种方式,根据指定位置删除以及指定元素删除两种方式。
iterator()
这里的iterator()只是很简单的迭代器,内部将remove/set/add三个修改操做进行了限制,由于这里的迭代器不能修改集合,代码就不细看了。注意到iterator并无加锁,由于iterator所访问的数组是不会变的,就算有其余线程对集合进行修改。
使用场景
CopyOnWriteArrayList适合读多写少的场景。经过空间换时间的方式来提升读的效率并保证写的安全性。