1. 程式人生 > 實用技巧 >寫入時複製(CopyOnWrite)與 讀寫鎖

寫入時複製(CopyOnWrite)與 讀寫鎖

一、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();
    }
}
private E get(Object[] a, int index) {
    return (E) a[index];
}

/**
 * {@inheritDoc}
 *
 * @throws IndexOutOfBoundsException {@inheritDoc}
 */
public E get(int index) {
    return get(getArray(), index);
}

三、優點和缺點

1.優點

對於一些讀多寫少的資料,寫入時複製的做法就很不錯,例如配置、黑名單、物流地址等變化非常少的資料,這是一種無鎖的實現。可以幫我們實現程式更高的併發。

CopyOnWriteArrayList 併發安全且效能比 Vector 好。Vector 是增刪改查方法都加了synchronized 來保證同步,但是每個方法執行的時候都要去獲得鎖,效能就會大大下降,而 CopyOnWriteArrayList 只是在增刪改上加鎖,但是讀不加鎖,在讀方面的效能就好於 Vector。

2.缺點

資料一致性問題。這種實現只是保證資料的最終一致性,不能保證資料的實時一致性;在新增到拷貝資料而還沒進行替換的時候,讀到的仍然是舊資料。

記憶體佔用問題。在進行寫操作的時候,記憶體裡會同時駐紮兩個物件的記憶體,如果物件比較大,頻繁地進行替換會消耗記憶體,從而引發 Java 的 GC 問題,這個時候,我們應該考慮其他的容器,例如 ConcurrentHashMap。

四、和讀寫鎖比較

讀寫鎖:分為讀鎖和寫鎖,多個讀鎖不互斥,讀鎖與寫鎖互斥。

總之,讀的時候上讀鎖,寫的時候上寫鎖! Java裡面的實現:ReentrantReadWriteLock

ReadWriteLock mylock = new ReentrantReadWriteLock(false);
  • 讀鎖lock、unlock:
myLock.readLock().lock();
myLock.readLock().unlock();
  • 寫鎖lock、unlock:
myLock.writeLock().lock();
myLock.writeLock().unlock();

總結:讀寫鎖是遵循寫寫互斥、讀寫互斥、讀讀不互斥的原則,而copyOnWrite則是寫寫互斥、讀寫不互斥、讀讀不互斥的原則。