SynchronizedMap和ConcurrentHashMap同步方式比較

 

 

在開始以前,先介紹下Map是什麼?html

javadoc中對Map的解釋以下:java

An object that maps keys to values . A map cannot contain duplicate keys; each key can map to at most one value. 安全

This interface takes the place of the Dictionary class, which was a totally abstract class rather than an interface.

The Map interface provides three collection views, which allow a map's contents to be viewed as a set of keys, collection of values, or set of key-value mappings.
 

從上可知,Map用於存儲「key-value」元素對,它將一個key映射到一個並且只能是惟一的一個value。Map能夠使用多種實現方式,HashMap的實現採用的是hash表;而TreeMap採用的是紅黑樹。多線程

1. Hashtable 和 HashMap併發

這兩個類主要有如下幾方面的不一樣:app

    Hashtable和HashMap都實現了Map接口,可是Hashtable的實現是基於Dictionary抽象類。ide

    在HashMap中,null能夠做爲鍵,這樣的鍵只有一個;能夠有一個或多個鍵所對應的值爲null。 當get()方法返回null值時,便可以表示 HashMap中沒有該鍵,也能夠表示該鍵所對應的值爲null。所以,在HashMap中不能由get()方法來判斷HashMap中是否存在某個鍵,而應該用containsKey()方法來判斷。而在Hashtable中,不管是key仍是value都不能爲null 。post

    這兩個類最大的不一樣在於Hashtable是線程安全的,它的方法是同步了的,能夠直接用在多線程環境中。而HashMap則不是線程安全的。在多線程環境中,須要手動實現同步機制。所以,在Collections類中提供了一個方法返回一個同步版本的HashMap用於多線程的環境:性能


1 public static <K,V> Ma<K,V> synchronizedMap(Map<K,V> m) {   
2     return new SynchronizedMap<K,V>(m);   
3  } 

該方法返回的是一個SynchronizedMap 的實例。SynchronizedMap類是定義在Collections中的一個靜態內部類。它實現了Map接口,並對其中的每個方法實現,經過synchronized 關鍵字進行了同步控制。線程

2. 潛在的線程安全問題

上面提到Collections爲HashMap提供了一個併發版本SynchronizedMap。這個版本中的方法都進行了同步,可是這並不等於這個類就必定是線程安全的。在某些時候會出現一些意想不到的結果。

以下面這段代碼:

 
1 // shm是SynchronizedMap的一個實例   
2 if(shm.containsKey('key')){   
3         shm.remove(key);   
4 }  

 這段代碼用於從map中刪除一個元素以前判斷是否存在這個元素。這裏的containsKey和reomve方法都是同步的,可是整段代碼卻不是。考慮這麼一個使用場景:線程A執行了containsKey方法返回true,準備執行remove操做;這時另外一個線程B開始執行,一樣執行了containsKey方法返回true,並接着執行了remove操做;而後線程A接着執行remove操做時發現此時已經沒有這個元素了。要保證這段代碼按咱們的意願工做,一個辦法就是對這段代碼進行同步控制,可是這麼作付出的代價太大。

在進行迭代時這個問題更改明顯。Map集合共提供了三種方式來分別返回鍵、值、鍵值對的集合:


1 Set<K> keySet();   
2   
3 Collection<V> values();   
4   
5 Set<Map.Entry<K,V>> entrySet(); 

 在這三個方法的基礎上,咱們通常經過以下方式訪問Map的元素:

 
1 Iterator keys = map.keySet().iterator();   
2   
3 while(keys.hasNext()){   
4         map.get(keys.next());   
5 }  

在這裏,有一個地方須要注意的是:獲得的keySet和迭代器都是Map中元素的一個「視圖」,而不是「副本」 。問題也就出如今這裏,當一個線程正在迭代Map中的元素時,另外一個線程可能正在修改其中的元素。此時,在迭代元素時就可能會拋出 ConcurrentModificationException異常。爲了解決這個問題一般有兩種方法,一是直接返回元素的副本,而不是視圖。這個能夠經過

集合類的 toArray() 方法實現,可是建立副本的方式效率比以前有所下降,特別是在元素不少的狀況下;另外一種方法就是在迭代的時候鎖住整個集合,這樣的話效率就更低了。

3. 更好的選擇:ConcurrentHashMap

java5中新增了ConcurrentMap接口和它的一個實現類ConcurrentHashMap。

ConcurrentHashMap提供了和Hashtable以及SynchronizedMap中所不一樣的鎖機制。Hashtable中採用的鎖機制是一次鎖住整個hash表,從而同一時刻只能由一個線程對其進行操做;而ConcurrentHashMap中則是一次鎖住一個桶。ConcurrentHashMap默認將hash表分爲16個桶,諸如get,put,remove等經常使用操做只鎖當前須要用到的桶。這樣,原來只能一個線程進入,如今卻能同時有16個寫線程執行,併發性能的提高是顯而易見的。

上面說到的16個線程指的是寫線程,而讀操做大部分時候都不須要用到鎖。只有在size等操做時才須要鎖住整個hash表。

在迭代方面,ConcurrentHashMap使用了一種不一樣的迭代方式。在這種迭代方式中,當iterator被建立後集合再發生改變就再也不是拋出ConcurrentModificationException,取而代之的是在改變時new新的數據從而不影響原有的數據 ,iterator完成後再將頭指針替換爲新的數據 ,這樣iterator線程能夠使用原來老的數據,而寫線程也能夠併發的完成改變。

原文地址:https://www.cnblogs.com/infinityu/articles/3188266.html

相關文章
相關標籤/搜索