Java 非線程安全的HashMap如何在多線程中使用
HashMap 是非線程安全的。在多線程條件下,容易導致死循環,具體表現為CPU使用率100%。因此多線程環境下保證 HashMap 的線程安全性,主要有如下幾種方法:
- 使用 java.util.Hashtable 類,此類是線程安全的。
- 使用 java.util.concurrent.ConcurrentHashMap,此類是線程安全的。
- 使用 java.util.Collections.synchronizedMap() 方法包裝 HashMap object,得到線程安全的Map,並在此Map上進行操作。
- 自己在程序的關鍵代碼段加鎖,保證多線程安全(不推薦)
接下來分析上面列舉的幾種方法實現並發安全的 HashMap 的原理:
(一)java.util.Hashtable類:
查看該類的源碼
public synchronized V get(Object key) { …… //具體的實現省略,請參考 jdk實現 } public synchronized V put(K key, V value) { …… //具體的實現省略,請參考 jdk實現 } public synchronized V remove(Object key) { …… //具體的實現省略,請參考 jdk實現 }
上面是 Hashtable 類提供的幾個主要方法,包括 get(),put(),remove() 等。注意到每個方法本身都是 synchronized 的,不會出現兩個線程同時對數據進行操作的情況,因此保證了線程安全性,但是也大大的降低了執行效率。因此是不推薦的。
(二)使用 java.util.concurrent.ConcurrentHashMap 類:
該類是 HashMap 的線程安全版,與 Hashtable 相比, ConcurrentHashMap 不僅保證了訪問的線程安全性,而且在效率上有較大的提高。
ConcurrentHashMap的數據結構如下:
可以看出,相對 HashMap 和 Hashtable, ConcurrentHashMap 增加了Segment 層,每個Segment 原理上等同於一個 Hashtable, ConcurrentHashMap 等同於一個 Segment 的數組。下面是 ConcurrentHashMap 的 put 和 get 方法:
final Segment<K,V> segmentFor(int hash) { return segments[(hash >>> segmentShift) & segmentMask]; } public V put(K key, V value) { if (value == null) throw new NullPointerException(); int hash = hash(key.hashCode()); return segmentFor(hash).put(key, hash, value, false); } public V get(Object key) { int hash = hash(key.hashCode()); return segmentFor(hash).get(key, hash); }
向 ConcurrentHashMap 中插入數據(put) 或者 讀取數據(get),首先都要將相應的 Key 映射到對應的 Segment,因此不用鎖定整個類, 只要對單個的 Segment 操作進行上鎖操作就可以了。理論上如果有 n 個 Segment,那么最多可以同時支持 n 個線程的並發訪問,從而大大提高了並發訪問的效率。