Java 中的寫時複製 (Copy on Write, COW)

Background

寫時複製 (Copy on Write, COW) 有時也叫 "隱式共享", 顧名思義, 就是讓全部須要使用資源 R 的使用者共享資源 R 的同一個副本, 當其中的某一個使用者要對資源 R 進行修改操做時, 先複製 R 的一個副本 R' , 再進行修改操做;java

Problem

在 Java 集合框架中, 像 ArrayList, HashSet 等基礎集合類是非線程安全的, 在多線程環境中同時進行遍歷和修改操做可能會出現 ConcurrentModificationException; 能夠對每一個操做都進行同步以解決這個問題, 但對於大部分操做是讀取數據的集合進行同步可能會使性能急劇降低, 在這種狀況下這種性能損失是沒有必要的;數組

舉個例子, 如下的座標中, x 軸表示時間軸, y 軸表示不一樣的線程, + 表示讀取操做, * 表示修改操做:安全

| ++ ++ ++   ++ ++ ++ ++
|++ + ++ +  ++ + +++ + +
|  ++ ++ *    ++ +* ++ +
| ++ + +   * ++ +  ++ + 
|+ + +++ *  + + +++ + ++
| + + +      + + + + + +
+----------------------
         1 2      3
複製代碼

其中除了 1 2 3 這三個時刻以外, 其餘時間都是隻有讀取操做, 在除了 1 2 3 以外的時間進行同步就是沒有必要的;多線程

所以咱們但願使用一種技術來處理那些在多線程環境下 "讀取操做" 遠遠多於 "修改操做" 的資源, 使得不須要經過對每個操做都進行同步;框架

Solution

Java 類庫中提供了兩個 Copy-on-Write 的類: CopyOnWriteArrayListCopyOnWriteArraySet, 分別實現了 ListSet 兩個接口;性能

How CopyOnWriteArrayList Works

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)"; 所以 CopyOnWriteArrayListCopyOnWriteArraySet 只能適用於對數據實時性要求不高的場景;

How CopyOnWriteArraySet Works

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);
}
複製代碼

是很是典型的 組合模式 應用;

相關文章
相關標籤/搜索