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適合讀多寫少的場景。經過空間換時間的方式來提升讀的效率並保證寫的安全性。