JAVA中寫時複製(Copy-On-Write)Map實現

1,什麼是寫時複製(Copy-On-Write)容器?html

寫時複製是指:在併發訪問的情景下,當須要修改JAVA中Containers的元素時,不直接修改該容器,而是先複製一份副本,在副本上進行修改。修改完成以後,將指向原來容器的引用指向新的容器(副本容器)。java

 

2,寫時複製帶來的影響安全

①因爲不會修改原始容器,只修改副本容器。所以,能夠對原始容器進行併發地讀。其次,實現了讀操做與寫操做的分離,讀操做發生在原始容器上,寫操做發生在副本容器上。多線程

②數據一致性問題:讀操做的線程可能不會當即讀取到新修改的數據,由於修改操做發生在副本上。但最終修改操做會完成並更新容器,所以這是最終一致性。併發

 

3,在JDK中提供了CopyOnWriteArrayList類和CopyOnWriteArraySet類,可是並無提供CopyOnWriteMap的實現。所以,能夠參考CopyOnWriteArrayList本身實現一個CopyOnWriteHashMapide

這裏主要是實現 在寫操做時,如何保證線程安全。高併發

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;


public class CopyOnWriteMap<K, V> implements Map<K, V>, Cloneable{

    private volatile Map<K, V> internalMap;
    
    public CopyOnWriteMap() {
        internalMap = new HashMap<K, V>(100);//初始大小應根據實際應用來指定
    }
    
    @Override
    public V put(K key, V value) {
        synchronized (this) {
            Map<K, V> newMap = new HashMap<K, V>(internalMap);//複製出一個新HashMap
            V val = newMap.put(key, value);//在新HashMap中執行寫操做
            internalMap = newMap;//將原來的Map引用指向新Map
            return val;
        }
    }
    
    @Override
    public void putAll(Map<? extends K, ? extends V> m) {
        synchronized (this) {
            Map<K, V> newMap = new HashMap<K, V>(internalMap);
            newMap.putAll(m);
            internalMap = newMap;
        }
        
    }
    
    @Override
    public V get(Object key) {
        V result = internalMap.get(key);
        return result;
    }
    ......//other methods inherit from interface Map
}

從上能夠看出,對於put() 和 putAll() 而言,須要加鎖。而讀操做則不須要,如get(Object key)。這樣,當一個線程須要put一個新元素時,它先鎖住當前CopyOnWriteMap對象,並複製一個新HashMap,而其餘的讀線程由於不須要加鎖,則可繼續訪問原來的HashMap。post

 

4,應用場景性能

CopyOnWrite容器適用於讀多寫少的場景。由於寫操做時,須要複製一個容器,形成內存開銷很大,也須要根據實際應用把握初始容器的大小。this

不適合於數據的強一致性場合。若要求數據修改以後當即能被讀到,則不能用寫時複製技術。由於它是最終一致性。

總結:寫時複製技術是一種很好的提升併發性的手段。

 

5,爲何會出現COW?

集合類(ArrayList、HashMap)上的經常使用操做是:向集合中添加元素、刪除元素、遍歷集合中的元素而後進行某種操做。當多個線程併發地對一個集合對象執行這些操做時就會引起ConcurrentModificationException,好比線程A在for-each中遍歷ArrayList,而線程B同時又在刪除ArrayList中的元素,就可能會拋出ConcurrentModificationException,能夠在線程A遍歷ArrayList時加鎖,但因爲遍歷操做是一種常見的操做,加鎖以後會影響程序的性能,所以for-each遍歷選擇了不對ArrayList加鎖而是當有多個線程修改ArrayList時拋出ConcurrentModificationException,所以,這是一種設計上的權衡。

爲了應對多線程併發修改這種狀況,一種策略就是本文的主題「寫時複製」機制;另外一種策略是:線程安全的容器類:

ArrayList--->CopyOnWriteArrayList

HashMap--->ConcurrentHashMap

而ConcurrentHashMap並非從「複製」這個角度來應對多線程併發修改,而是引入了分段鎖(JDK7);CAS、鎖(JDK11)解決多線程併發修改的問題。

 

參考資料:

聊聊併發-Java中的Copy-On-Write容器

Java 集合系列10之 HashMap詳細介紹(源碼解析)和使用示例

相關文章
相關標籤/搜索