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 一如繼往喜歡使用現成的類來實現。
相關文章
關注微信公衆號,聊點其餘的