寫時複製 (Copy on Write, COW) 有時也叫 "隱式共享", 顧名思義, 就是讓全部須要使用資源 R 的使用者共享資源 R 的同一個副本, 當其中的某一個使用者要對資源 R 進行修改操做時, 先複製 R 的一個副本 R' , 再進行修改操做;java
在 Java 集合框架中, 像 ArrayList
, HashSet
等基礎集合類是非線程安全的, 在多線程環境中同時進行遍歷和修改操做可能會出現 ConcurrentModificationException
; 能夠對每一個操做都進行同步以解決這個問題, 但對於大部分操做是讀取數據的集合進行同步可能會使性能急劇降低, 在這種狀況下這種性能損失是沒有必要的;數組
舉個例子, 如下的座標中, x 軸表示時間軸, y 軸表示不一樣的線程, +
表示讀取操做, *
表示修改操做:安全
| ++ ++ ++ ++ ++ ++ ++
|++ + ++ + ++ + +++ + +
| ++ ++ * ++ +* ++ +
| ++ + + * ++ + ++ +
|+ + +++ * + + +++ + ++
| + + + + + + + + +
+----------------------
1 2 3
複製代碼
其中除了 1 2 3 這三個時刻以外, 其餘時間都是隻有讀取操做, 在除了 1 2 3 以外的時間進行同步就是沒有必要的;多線程
所以咱們但願使用一種技術來處理那些在多線程環境下 "讀取操做" 遠遠多於 "修改操做" 的資源, 使得不須要經過對每個操做都進行同步;框架
Java 類庫中提供了兩個 Copy-on-Write 的類: CopyOnWriteArrayList
和 CopyOnWriteArraySet
, 分別實現了 List
和 Set
兩個接口;性能
CopyOnWriteArrayList
只有在對其進行修改操做時纔會進行同步操做, 所以其 add
, remove
等方法中均使用了同步機制; 在 CopyOnWriteArrayList
中, 定義了一個可重入鎖:this
final transient ReentrantLock lock = new ReentrantLock();
複製代碼
該鎖用於對全部修改集合的方法 (add
, remove
等) 進行同步, 在進行實際修改操做時, 會先複製原來的數組, 再進行修改, 最後替換原來的:spa
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();
}
}
複製代碼
因爲在修改時複製了一份數據, 所以全部讀取操做都無需進行同步:線程
public E get(int index) {
return get(getArray(), index);
}
複製代碼
但也會所以引入 "弱一致性" 問題; 所謂 "弱一致性" 是指當一個線程正在讀取數據時, 若此時有另外一個線程同時在修改該區域的數據, 讀取的線程將沒法讀取最新的數據, 即該讀取線程只能讀取到它讀取時刻之前的最新數據;code
"弱一致性" 的另外一個體現是當使用迭代器的時候, 使用迭代器遍歷集合時, 該迭代器只能遍歷到建立該迭代器時的數據, 對於建立了迭代器後對集合進行的修改, 該迭代器沒法感知; 這是由於建立迭代器時, 迭代器對原始數據建立了一份 "快照 (Snapshot)"; 所以 CopyOnWriteArrayList
和 CopyOnWriteArraySet
只能適用於對數據實時性要求不高的場景;
CopyOnWriteArraySet
的實現是基於 CopyOnWriteArrayList
的, 其內部維護了一個 CopyOnWriteArrayList
實例 al
:
private final CopyOnWriteArrayList<E> al;
public CopyOnWriteArraySet() {
al = new CopyOnWriteArrayList<E>();
}
複製代碼
全部對 CopyOnWriteArraySet
的操做都被委託給 al
, 如 add
方法:
public boolean add(E e) {
return al.addIfAbsent(e);
}
複製代碼
是很是典型的 組合模式 應用;