一、CopyOnWrite容器有兩種:
·CopyOnWriteArrayList
·CopyOnWriteArraySet
CopyOnWrite容器簡稱COW容器,其特色以下:
1)CopyOnWrite容器即寫時複製的容器。
2)通俗的理解是當咱們往一個容器添加元素的時候,不直接往當前容器添加,而是先將當前容器進行Copy,複製出一個新的容器,而後新的容器裏添加元素,添加完元素以後,再將原容器的引用指向新的容器,讀時都是訪問舊容器,寫時才須要加鎖。
3)這樣作的好處是咱們能夠對CopyOnWrite容器進行併發的讀,而不須要加鎖,由於當前容器不會添加任何元素。
因此CopyOnWrite容器也是一種讀寫分離的思想,讀和寫不一樣的容器。
二、CopyOnWriteArrayList類繼承結構:
public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable;java
三、對於CopyOnWriteArrayList主要查看如下幾個方法:
·建立:CopyOnWriteArrayList()
·添加元素:即add(E)方法
·獲取單個對象:即get(int)方法
·刪除對象:即remove(E)方法
·遍歷全部對象:即iterator(),在實際中更經常使用的是加強型的for循環去作遍歷。數組
四、構造方法CopyOnWriteArrayList,其相關代碼:
/* 只能 getArray/setArray訪問數組. /
private volatile transient Object[] array;併發
/** * 獲取數組 */ final Object[] getArray() { return array; } /** * 設置數組 */ final void setArray(Object[] a) { array = a; } /** *建立一個空的數組,Object[0],而ArrayList則是10 */ public CopyOnWriteArrayList() { setArray(new Object[0]); }
五、添加元素
public boolean add(E e) {dom
final ReentrantLock lock = this.lock; lock.lock(); //獲取全局鎖(獨佔鎖),獲取不到則阻塞 try { Object[] elements = getArray(); //獲取當前的數組 int len = elements.length; /* * Arrays.copyOf(elements, len + 1)的大體執行流程: * 1)建立新數組,容量爲len+1, * 2)將舊數組elements拷貝到新數組, * 3)返回新數組 */ Object[] newElements = Arrays.copyOf(elements, len + 1); newElements[len] = e; //新數組的末尾元素設成e setArray(newElements); //將舊數組指向新數組引用 return true; } finally { lock.unlock(); } } 六、獲取指定元素 @SuppressWarnings("unchecked") private E get(Object[] a, int index) { return (E) a[index]; } /** * {@inheritDoc} * * @throws IndexOutOfBoundsException {@inheritDoc} //找不到指定的元素則拋出異常 */ public E get(int index) { return get(getArray(), index); //先獲取數組,在讀取指定位置的元素。 } 從以上代碼能夠看出,讀取元素時不加鎖的,此時採用的是弱一致性策略。獲取指定位置的元素分爲兩步,首先獲取到當前list裏面的array數組,這裏稱爲步驟1,而後經過隨機訪問的下標方式訪問指定位置的元素,這裏稱爲步驟2。 由於整個過程並無加鎖,這就可能會致使當執行完步驟1後執行步驟2前,另一個線程C進行了修改操做,好比remove操做,就會進行寫時拷貝刪除當前get方法要訪問的元素,而且修改當前list的array爲新數組。而這以後步驟2 可能纔開始執行,步驟2操做的是線程C刪除元素前的一個快照數組(由於步驟1讓array指向的是原來的數組),因此雖然線程C已經刪除了index處的元素,可是步驟2仍是返回index處的元素,這其實就是寫時拷貝策略帶來弱一致性。
七、修改指定元素,若元素不存在則拋出ndexOutOfBoundsException。
/**ide
@throws IndexOutOfBoundsException {@inheritDoc}
*/
public E set(int index, E element) {
final ReentrantLock lock = this.lock;
lock.lock();//獨佔鎖
try {
Object[] elements = getArray();
E oldValue = get(elements, index); //獲取指定位置的元素ui
if (oldValue != element) { int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len); newElements[index] = element; //更新指定位置上的元素 setArray(newElements); } else { // Not quite a no-op; ensures volatile write semantics 同一對象,則不更新, setArray(elements); } return oldValue; } finally { lock.unlock(); }
}
若是指定位置元素與新值同樣,則爲了保障volatile語義,仍是須要從新設置下array,雖然array內容並無改變(爲了保證 volatile 語義是考慮到 set 方法自己應該提供 volatile 的語義).。this
七、刪除元素 public E remove(int index) { final ReentrantLock lock = this.lock; lock.lock(); //加獨佔鎖,若獲取不到則阻塞 try { Object[] elements = getArray(); int len = elements.length; E oldValue = get(elements, index); int numMoved = len - index - 1; //判斷是否是最後一個元素 if (numMoved == 0) setArray(Arrays.copyOf(elements, len - 1)); else { Object[] newElements = new Object[len - 1]; //分兩次拷貝刪除後的數組 //先複製index位置以前的元素, System.arraycopy(elements, 0, newElements, 0, index); //index在此處表示要複製的長度或元素個數 //複製inex位置以後的元素 System.arraycopy(elements, index + 1, newElements, index, numMoved); //其中:src表示源數組,srcPos表示源數組要複製的起始位置,desc表示目標數組,length表示要複製的長度。 setArray(newElements); } return oldValue; } finally { lock.unlock(); //操做完成,解除鎖定 }
}線程
八、弱一致性的迭代器
/**指針
@return an iterator over the elements in this list in proper sequence
*/
public Iterator<E> iterator() {
return new COWIterator<E>(getArray(), 0);
}code
/**
<tt>remove</tt>, <tt>set</tt> or <tt>add</tt> methods. 不支持remove/set/add等操做
*/
public ListIterator<E> listIterator() {
return new COWIterator<E>(getArray(), 0);
}
/**
@throws IndexOutOfBoundsException {@inheritDoc}
*/
public ListIterator<E> listIterator(final int index) {
Object[] elements = getArray();
int len = elements.length;
if (index<0 || index>len)
throw new IndexOutOfBoundsException("Index: "+index);
return new COWIterator<E>(elements, index);
}
private static class COWIterator<E> implements ListIterator<E> {
/* Snapshot of the array /array數組的一個快照
private final Object[] snapshot;
/* Index of element to be returned by subsequent call to next. /將由後續調用next返回的元素的索引。即數組下標或索引
private int cursor;
private COWIterator(Object[] elements, int initialCursor) { cursor = initialCursor; snapshot = elements; } //判斷是否有下個元素 public boolean hasNext() { return cursor < snapshot.length; } ...... //獲取當前元素,索引加1 @SuppressWarnings("unchecked") public E next() { if (! hasNext()) throw new NoSuchElementException(); return (E) snapshot[cursor++]; } .....
}
這裏爲何說snapshot是list的快照呢?明明是指針傳遞的引用,而不是拷貝。若是在該線程使用返回的迭代器遍歷元素的過程當中,其餘線程沒有對list進行增刪改,那麼snapshot自己就是list的array,由於它們是引用關係。
可是若是遍歷期間,有其餘線程對該list進行了增刪改,那麼snapshot就是快照了,由於增刪改後list裏面的數組被新數組替換了,這時候老數組只有被snapshot所引用,因此這也就說明獲取迭代器後,使用改迭代器進行遍歷元素時候,其它線程對該list進行的增刪改是不可見的,由於它們操做的是兩個不一樣的數組,這也就是弱一致性的達成。