HashMap、HashTable與ConcurrentHashMap的區別

1、HashTable與HashMap

(1)HashTable和HashMap都實現了Map接口,但是HashTable的實現是基於Dictionary抽象類。

(2)在HashMap中,null可以作為鍵,這樣的鍵只有一個;可以有一個或多個鍵所對應的值為null。當get()方法返回null值時,既可以表示HashMap中沒有該鍵,也可以表示該鍵所對應的值為null。因此,在HashMap中不能由get()方法來判斷HashMap中是否存在某個鍵,而應該用containsKey()方法來判斷。而在HashTable中,無論是key還是value都不能為null。

(3)HashTable是線程安全的,它的方法是同步了的,可以直接用在多線程環境中。HashMap則不是線程安全的。在多線程環境中,需要手動實現同步機制。

因此,在Collections類中提供了一個方法返回一個同步版本的HashMap用於多線程的環境:

HashMap、HashTable與ConcurrentHashMap的區別

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

2、潛在的線程安全問題

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

如下面這段代碼:

HashMap、HashTable與ConcurrentHashMap的區別

這段代碼用於從map中刪除一個元素之前判斷是否存在這個元素。這裡的containsKey()和remove()方法都是同步的,但是整段代碼卻不是。

考慮這麼一個場景:線程A執行了containsKey()方法返回true,準備執行remove()操作;這時另一個線程B開始執行,同樣執行了containsKey()方法返回true,並接著執行了remove()操作;然後線程A接著執行remove操作時發現此時已經沒有這個元素了。

要保證上面這個場景下代碼按我們的醫院工作,一個辦法就是對這段代碼進行同步控制,但是這付出的代價太大了。在進行迭代時這個問題更加明顯。

Map集合共提供了三種方式來分別返回鍵、值、鍵值對的集合:

HashMap、HashTable與ConcurrentHashMap的區別

在這三個方法基礎上,我們一般通過如下方式訪問Map的元素:

HashMap、HashTable與ConcurrentHashMap的區別

在這裡,有一個地方需要注意的是:得到的keySet和迭代器都是Map中元素的一個“視圖”,而不是“副本”。問題也就出現在這裡,當一個線程正在迭代Map中的元素時,另一個線程可能正在修改其中的元素。此時,在迭代元素時就可能拋出ConcurrentModificationException異常。

為了解決這個問題,通常有兩種方法:

(1)一是直接返回元素的副本,而不是視圖。這個可以通過集合類的toArray()方法實現,但是創建副本的方式效率比之前有所降低,特別是在元素很多的情況下;

(2)另一種方法就是在迭代的時候鎖住整個集合,這樣的話效率就更低了。

3、更好的選擇:ConcurrentHashMap

Java 5中新增了ConcurrentMap接口和它的實現類ConcurrentHashMap。

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

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

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

HashMap、HashTable與ConcurrentHashMap的區別


分享到:


相關文章: