1. 程式人生 > 實用技巧 >多執行緒下如何安全訪問hashmap

多執行緒下如何安全訪問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 的執行緒安全性,主要有如下幾種方法:

  1. 使用 java.util.Hashtable 類,此類是執行緒安全的。

  2. 使用 java.util.concurrent.ConcurrentHashMap,此類是執行緒安全的。

  3. 使用 java.util.Collections.synchronizedMap() 方法包裝 HashMap object,得到執行緒安全的Map,並在此Map上進行操作。

  4. 自己在程式的關鍵程式碼段加鎖,保證多執行緒安全(不推薦)

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 個執行緒的併發訪問,從而大大提高了併發訪問的效率。