一、CopyOnWrite 思想
寫入時復制(CopyOnWrite,簡稱COW)思想是計算機程序設計領域中的一種通用優化策略。其核心思想是,如果有多個調用者(Callers)同時訪問相同的資源(如內存或者是磁盤上的數據存儲),他們會共同獲取相同的指針指向相同的資源,直到某個調用者修改資源內容時,系統才會真正復制一份專用副本(private copy)給該調用者,而其他調用者所見到的最初的資源仍然保持不變。這過程對其他的調用者都是透明的(transparently)。此做法主要的優點是如果調用者沒有修改資源,就不會有副本(private copy)被創建,因此多個調用者只是讀取操作時可以共享同一份資源。
通俗易懂的講,寫入時復制技術就是不同進程在訪問同一資源的時候,只有更新操作,才會去復制一份新的數據並更新替換,否則都是訪問同一個資源。
JDK 的 CopyOnWriteArrayList/CopyOnWriteArraySet 容器正是采用了 COW 思想,它是如何工作的呢?簡單來說,就是平時查詢的時候,都不需要加鎖,隨便訪問,只有在更新的時候,才會從原來的數據復制一個副本出來,然后修改這個副本,最后把原數據替換成當前的副本。修改操作的同時,讀操作不會被阻塞,而是繼續讀取舊的數據。這點要跟讀寫鎖區分一下。
二、源碼分析
我們先來看看 CopyOnWriteArrayList 的 add() 方法,其實也非常簡單,就是在訪問的時候加鎖,拷貝出來一個副本,先操作這個副本,再把現有的數據替換為這個副本。
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();
}
}
CopyOnWriteArrayList 的 get(int index) 方法就是普通的無鎖訪問。
public E get(int index) {
return get(getArray(), index);
}
@SuppressWarnings("unchecked")
private E get(Object[] a, int index) {
return (E) a[index];
}
三、優點和缺點
1.優點
對於一些讀多寫少的數據,寫入時復制的做法就很不錯,例如配置、黑名單、物流地址等變化非常少的數據,這是一種無鎖的實現。可以幫我們實現程序更高的並發。
CopyOnWriteArrayList 並發安全且性能比 Vector 好。Vector 是增刪改查方法都加了synchronized 來保證同步,但是每個方法執行的時候都要去獲得鎖,性能就會大大下降,而 CopyOnWriteArrayList 只是在增刪改上加鎖,但是讀不加鎖,在讀方面的性能就好於 Vector。
2.缺點
數據一致性問題。這種實現只是保證數據的最終一致性,在添加到拷貝數據而還沒進行替換的時候,讀到的仍然是舊數據。
內存占用問題。如果對象比較大,頻繁地進行替換會消耗內存,從而引發 Java 的 GC 問題,這個時候,我們應該考慮其他的容器,例如 ConcurrentHashMap。