同樣是線程安全,ConcurrentHashMap 和 Hashtable 區別?
前言
通過之前的文章《HashMap 爲什麼線程不安全?》,我們都知道 HashMap 不是線程安全的,而本章中 ConcurrentHashMap 和 Hashtable 它們兩個都是線程安全的,那它們有哪些不同點呢?我們從以下四個角度出發,去分析它們的不同點。
1.出現的版本不同
我們先從表面的、顯而易見的出現時間來分析。Hashtable 在 JDK1.0 的時候就存在了,並在 JDK1.2 版本中實現了 Map 接口,成爲了集合框架的一員。而 ConcurrentHashMap 則是在 JDK1.5 中才出現的,也正是因爲它們出現的年代不同,而後出現的往往是對前面出現的類的優化,所以它們在實現方式以及性能上,也存在着較大的不同。
2.實現線程安全的方式不同
Hashtable 實現併發安全的原理是通過 synchronized 關鍵字,Collections.SynchronizedMap(new HashMap()) 的原理和 Hashtable 類似,也是利用 synchronized 實現的。
hashtable 部分源碼如下:
public synchronized V get(Object key) {...}
public synchronized V put(K key, V value) {...}
public synchronized V remove(Object key) {...}
...
ConcurrentHashMap 實現線程安全的原理是利用了 CAS + synchronized + Node 節點的方式,這和 Hashtable 的完全利用 synchronized 的方式有很大的不同。
3.性能不同
正因爲它們在線程安全的實現方式上的不同,導致它們在性能方面也有很大的不同。當線程數量增加的時候,Hashtable 的性能會急劇下降,因爲每一次修改都需要鎖住整個對象,而其他線程在此期間是不能操作的。不僅如此,還會帶來額外的上下文切換等開銷,所以此時它的吞吐量甚至還不如單線程的情況。
而在 ConcurrentHashMap 中,就算上鎖也僅僅會對一部分上鎖而不是全部都上鎖,所以多線程中的吞吐量通常都會大於單線程的情況,也就是說,在併發效率上,ConcurrentHashMap 比 Hashtable 提高了很多。
4.迭代時修改的不同
Hashtable(包括 HashMap)不允許在迭代期間修改內容,否則會拋出ConcurrentModificationException 異常,其原理是檢測 modCount 變量,迭代器的 next() 方法的代碼如下:
public T next() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
return nextElement();
}
在遍歷元素的時候,會先判斷 modCount != expectedModCount
,expectedModCount 表示當前 Hashtable 被修改的次數,這個值在迭代器生成的時候被賦值 protected int expectedModCount = modCount;
,而每一次去調用 Hashtable 的包括 addEntry()、remove()、rehash() 等方法中,都會修改 modCount 的值。這樣一來,迭代器在進行 next 的時候,也可以感知到,於是它就會發現 modCount 不等於 expectedModCount,就會拋出 ConcurrentModificationException 異常。
相反,ConcurrentHashMap 即便在迭代期間修改內容,也不會拋出ConcurrentModificationException。
5.總結
本課時總結了 ConcurrentHashMap 與 Hashtable 的區別,雖然它們都是線程安全的,但是在出現的版本上、實現線程安全的方式上、性能上,以及迭代時是否支持修改等方面都有較大的不同,如果我們有併發的場景,那麼使用 ConcurrentHashMap 是最合適的,相反,Hashtable 已經不再推薦使用。
6.參考
- 《Java 併發編程 78 講》- 徐隆曦