首先須要明確的是,無論使用那種Map,都不能保證公共混合調用的線程安全,只能保證單條操做的線程安全,在這一點上各Map不存在優劣。html
前文中簡單說過HashTable和synchronizedMap,其實這兩個類不須要說太多,把代碼貼一下相信看過Java多線程的就能很容易理解了。java
HashTable的話,實現這個樣子的。能夠看到的是,對於Hash表的全部操做,HashTable都加了鎖,但也只能保證單條操做的線程安全。數組
public synchronized V get(Object key) { // 省略實現 } public synchronized V put(K key, V value) { // 省略實現 }
synchronizedMap的實現以下,沒直接在方法上加,儘管其實質與HashTable是等效的,也一樣有HashTable的缺陷,但synchronizedMap給用戶留下了選擇的空間:用戶能夠在不須要加鎖時直接操做原始Map,在實際編碼時就能夠基於這點進行優化。安全
// synchronizedMap方法 public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) { return new SynchronizedMap<>(m); } // SynchronizedMap類 private static class SynchronizedMap<K,V> implements Map<K,V>, Serializable { private static final long serialVersionUID = 1978198479659022715L; private final Map<K,V> m; // Backing Map final Object mutex; // Object on which to synchronize SynchronizedMap(Map<K,V> m) { this.m = Objects.requireNonNull(m); mutex = this; } SynchronizedMap(Map<K,V> m, Object mutex) { this.m = m; this.mutex = mutex; } public int size() { synchronized (mutex) {return m.size();} } public boolean isEmpty() { synchronized (mutex) {return m.isEmpty();} } public boolean containsKey(Object key) { synchronized (mutex) {return m.containsKey(key);} } public boolean containsValue(Object value) { synchronized (mutex) {return m.containsValue(value);} } public V get(Object key) { synchronized (mutex) {return m.get(key);} } public V put(K key, V value) { synchronized (mutex) {return m.put(key, value);} } public V remove(Object key) { synchronized (mutex) {return m.remove(key);} } // 省略其餘方法 }
提升安全HashMap的併發性的方法,能夠經過減少鎖粒度的方式,不對整個Hash表加鎖,而是對每一個bucket加鎖,甚至用鎖池,每一個鎖維護幾個bucket,讓Map的不一樣部分能夠被多個線程訪問,不過這樣的方式會讓對總體集合操做的方法的實現更加困難。Java7中的ConcurrentHashMap就經過Segment引入了這個分段加鎖概念,但Java8因爲上述困難更改了機制,引入了紅黑樹結構,去掉了Segment。數據結構
JDK1.8的改進後,ConcurrentHashMap的寫性能有10%左右的下降,但讀性能有了很大提高。主要是將過於集中的hash節點的效率從O(N)提升到了O(LOGN)。多線程
ConcurrentHashMap利用了CAS進行實現,從而以樂觀鎖的方式實現了線程安全的HashMap,concurrentHashMap的源碼很複雜,一些方法的實現思路以下:併發
Java8的ConcurrentHashMap的數據結構實現思路大概爲,對於Hash表中每個節點,其數據結構能夠爲單節點,鏈表數組或紅黑樹,隨着節點中元素增長而改變。(改變方法見treeifyBin)。性能
該方法用於對數組鏈表擴容,或將鏈表結構轉化爲紅黑樹,一個節點的元素個數大於鏈表閾值(默認8)時,若是數組鏈表長度小於紅黑樹閾值(默認64),則對數組鏈表擴容,不然將該節點轉換爲紅黑樹。優化
這些方法負責hash表擴容,因爲要經過CAS實現線程安全,代碼十分複雜。大概思路爲,原數組長度爲n,則產生n個遷移任務,讓每個線程負責一個小任務,以後監測是否有其餘沒作完的任務,幫助遷移。ui
get方法不涉及CAS操做,實現較爲簡單,計算hash值,找到對應節點進行判斷:
目前多線程環境下ConcurrentMap的性能有很高的優越性,一般狀況下,若是你的Map處於多讀少寫的場景,優先考慮ConcurrentMap,但在多寫少讀的情境中,因爲資源競爭激烈,CAS自旋可能致使ConcurrentMap性能不如synchronizedMap。
Collections.synchronizedMap()、ConcurrentHashMap、Hashtable之間的區別
Java8 ConcurrentHashMap詳解
淺談Java8中的ConcurrentHashMap
SynchronizedMap