多執行緒下如何安全訪問hashmap
1
執行緒同步指的是多執行緒的協同,定義多個執行緒如何訪問特定資源,避免多執行緒併發訪問導致資料不一致的問題。ArrayList、LinkkedList、HashMap是最常用的資料結構,但是他們是執行緒不安全的,在多執行緒場景下,如果不做控制,可能會到導致執行緒安全問題。
解決ArrayList、LinkedList執行緒安全方法
1、繼承Arraylist,然後重寫或按需求編寫自己的方法,這些方法要寫成synchronized,在這些synchronized的方法中呼叫ArrayList的方法。
2、使用Collections.synchronizedList();使用方法如下:
假如你建立的程式碼如下:List> data=new ArrayList>();
那麼為了解決這個執行緒安全問題你可以這麼使用Collections.synchronizedList(),如:
List> data=Collections.synchronizedList(new ArrayList>());
其他的都沒變,使用的方法也幾乎與ArrayList一樣,大家可以參考下api文件;
額外說下 ArrayList與LinkedList;這兩個都是介面List下的一個實現,用法都一樣,但用的場所的有點不同,ArrayList適合於進行大量的隨機訪問的情況下使用,LinkedList適合在表中進行插入、刪除時使用,二者都是非執行緒安全,解決方法同上(為了避免執行緒安全,以上採取的方法,特別是第二種,其實是非常損耗效能的)。
2
解決HashMap執行緒安全方法
1、繼承HashMap,重寫或者按要求編寫自己的方法,這些方法要寫成synchronized,在這些synchronized的方法中呼叫HashMap的方法
2、使用Collections.synchronizedMap()
3、使用ConcurrentHashMap替代,並不推薦新程式碼使用Hashtable,HashTable繼承於Dictionary,任意時間只有一個執行緒能寫Hashtable,併發效能不如ConcurrentHashMap,因為ConcurrentHashMap引入了分段鎖。不需要執行緒安全的場景使用HashMap,需要執行緒安全的場合使用ConcurrentHashMap替換。
HashMap 是非執行緒安全的。在多執行緒條件下,容易導致死迴圈,具體表現為CPU使用率100%。因此多執行緒環境下保證 HashMap 的執行緒安全性,主要有如下幾種方法:
-
使用 java.util.Hashtable 類,此類是執行緒安全的。
-
使用 java.util.concurrent.ConcurrentHashMap,此類是執行緒安全的。
-
使用 java.util.Collections.synchronizedMap() 方法包裝 HashMap object,得到執行緒安全的Map,並在此Map上進行操作。
-
自己在程式的關鍵程式碼段加鎖,保證多執行緒安全(不推薦)
3
接下來分析上面列舉的幾種方法實現併發安全的 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 個執行緒的併發訪問,從而大大提高了併發訪問的效率。