HashMap HashTable ConcurrentHashMap


1. Hashtable 和 HashMap


(1)區別,這兩個類主要有以下幾方面的不同:
Hashtable和HashMap都實現了Map接口,但是Hashtable的實現是基於Dictionary抽象類。
 
在HashMap中,null可以作為鍵,這樣的鍵只有一個;可以有一個或多個鍵所對應的值為null。 
當get()方法返回null值時,即可以表示 HashMap中沒有該鍵,也可以表示該鍵所對應的值為null。
 因此,在HashMap中不能由get()方法來判斷HashMap中是否存在某個鍵,而應該用containsKey()方法來判斷。
 而在Hashtable中,無論是key還是value都不能為null 。
 
   這兩個類最大的不同在於:
(1)Hashtable是線程安全的,它的方法是同步了的,可以直接用在多線程環境中。
(2)而HashMap則不是線程安全的。在多線程環境中,需要手動實現同步機制。





因此,在Collections類中提供了一個方法返回一個同步版本的HashMap用於多線程的環境:
Java代碼
public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) {   
 return new SynchronizedMap<K,V>(m);   
 }  
該方法返回的是一個SynchronizedMap 的實例。
SynchronizedMap類是定義在Collections中的一個靜態內部類。
它實現了Map接口,並對其中的每一個方法實現,通過synchronized 關鍵字進行了同步控制。
 
2. 潛在的線程安全問題
上面提到Collections為HashMap提供了一個並發版本SynchronizedMap。
這個版本中的方法都進行了同步,但是這並不等於這個類就一定是線程安全的。
在某些時候會出現一些意想不到的結果。
如下面這段代碼:
Java代碼
// shm是SynchronizedMap的一個實例   
if(shm.containsKey('key')){   
        shm.remove(key);   
}  
 這段代碼用於從map中刪除一個元素之前判斷是否存在這個元素。
 這里的containsKey和reomve方法都是同步的,但是整段代碼卻不是。
 
 考慮這么一個使用場景:
線程A執行了containsKey方法返回true,准備執行remove操作;
這時另一個線程B開始執行,同樣執行了containsKey方法返回true,並接着執行了remove操作;
然后線程A接着執行remove操作時發現此時已經沒有這個元素了。
要保證這段代碼按我們的意願工作,一個辦法就是對這段代碼進行同步控制,但是這么做付出的代價太大。
 
在進行迭代時這個問題更改明顯。Map集合共提供了三種方式來分別返回鍵、值、鍵值對的集合:
Java代碼
Set<K> keySet();   
  
Collection<V> values();   
  
Set<Map.Entry<K,V>> entrySet();  


 在這三個方法的基礎上,我們一般通過如下方式訪問Map的元素:
Java代碼
Iterator keys = map.keySet().iterator();   
  
while(keys.hasNext()){   
        map.get(keys.next());   
}  
 
在這里,有一個地方需要注意的是:得到的keySet和迭代器都是Map中元素的一個“視圖”,而不是“副本” 。
問題也就出現在這里,當一個線程正在迭代Map中的元素時,另一個線程可能正在修改其中的元素。
此時,在迭代元素時就可能會拋出 ConcurrentModificationException異常。


為了解決這個問題通常有兩種方法,
(1)一是直接返回元素的副本,而不是視圖。這個可以通過
集合類的 toArray() 方法實現,但是創建副本的方式效率比之前有所降低,
特別是在元素很多的情況下;
(2)另一種方法就是在迭代的時候鎖住整個集合,這樣的話效率就更低了。


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線程可以使用原來老的數據。

而寫線程也可以並發的完成改變。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM