面試之Hashtable和ConcurrentHashMap

那麼要如何保證HashMap的線程安全呢? 方法有不少,好比使用Hashtable或者Collections.synchronizedMap,可是這兩位選手都有一個共同的問題:性能。由於不論是讀仍是寫操做,他們都會給整個集合上鎖,致使同一時間的其餘操做被阻塞。數據庫


雖然Hashtable和Collections.synchronizedMap解決了HashMap的線程不安全的問題,可是帶來了運行效率不佳的問題。 數組


基於以上所述,兼顧了線程安全和運行效率的ConcurrentHashMap就出現了。安全

在瞭解了HashMap以後,接下來就開始瞭解一下ConcurrentHashMap。
ConcurrentHashMap與HashMap相比,最關鍵的是要理解一個概念:segment
Segment其實就是一個Hashmap 。Segment也包含一個HashEntry數組,數組中的每個HashEntry既是一個鍵值對,也是一個鏈表的頭節點。
Segment對象在ConcurrentHashMap集合中有2的N次方個,共同保存在一個名爲segments的數組當中。(類比HashMap來理解Segment就好)markdown

所以ConcurrentHashMap的結構爲:
這裏寫圖片描述
換言之,ConcurrentHashMap是一個雙層哈希表。在一個總的哈希表下面,有若干個子哈希表。(這樣的雙層結構,相似於數據庫水平拆分來理解)多線程

ConcurrentHashMap如此的設計,優點主要在於:
每一個segment的讀寫是高度自治的,segment之間互不影響。這稱之爲「鎖分段技術」;併發

看一下併發狀況下的ConcurrentHashMap:
情景一:不一樣segment的併發寫入性能

這裏寫圖片描述
不一樣的Segment是能夠併發執行put操做的.net

情景二:同一segment的併發寫入
這裏寫圖片描述
由於segment的寫入是上鎖的,所以對 同一segment的併發寫入會被阻塞;線程

情景三:同一segment的一寫一讀
這裏寫圖片描述
同一segment的寫和讀是能夠併發執行的;設計


看到此處,就已經知道了ConcurrentHashMap的併發狀況,有興趣的話能夠繼續看下ConcurrentHashMap的具體讀寫過程。

Get方法:

1.爲輸入的Key作Hash運算,獲得hash值。

2.經過hash值,定位到對應的Segment對象

3.再次經過hash值,定位到Segment當中數組的具體位置。

Put方法:

1.爲輸入的Key作Hash運算,獲得hash值。

2.經過hash值,定位到對應的Segment對象

3.獲取可重入鎖

4.再次經過hash值,定位到Segment當中數組的具體位置。

5.插入或覆蓋HashEntry對象。

6.釋放鎖。


看到此處,對於ConcurrentHashMap的Get和Put的過程(讀寫過程)就有了一個完整的瞭解了。
基於上述,會有一個問題:
每個segment各自持有鎖,那麼在調用size()方法的時候(size()在實際開發大量使用),怎麼保持一致性呢
詳細描述一下上面問題的情景:
Size方法的目的是統計ConcurrentHashMap的總元素數量, 確定要把每一個segment內部的元素數量都加起來
那麼假設一種狀況,在統計segment元素數量的過程當中,在統計結束前,已統計過的segment插入了新的元素,size()返回的數量就會出現不一致的問題。
爲解決這個問題,ConcurrentHashMap的Size()方法是經過一個嵌套循環解決的,大致過程以下:
1.遍歷全部的Segment。

2.把Segment的元素數量累加起來。

3.把Segment的修改次數累加起來。

4.判斷全部Segment的總修改次數是否大於上一次的總修改次數。若是大於,說明統計過程當中有修改,從新統計,嘗試次數+1;若是不是。說明沒有修改,統計結束。

5.若是嘗試次數超過閾值,則對每個Segment加鎖,再從新統計。

6.再次判斷全部Segment的總修改次數是否大於上一次的總修改次數。因爲已經加鎖,次數必定和上次相等。

7.釋放鎖,統計結束。

這種解決辦法是否是似曾相識?沒錯,這種思想和樂觀鎖悲觀鎖的思想一模一樣(不熟悉樂觀鎖的道友能夠看我轉的一篇很是生動的介紹,傳送門

爲了避免鎖全部segment,首先樂觀地假設size過程當中不會有修改。當嘗試必定次數,才無奈轉悲觀,鎖住全部segment以保證一致性。

補充:
一、以上都是基於Java1.7的ConcurrentHashMap原理和代碼;

二、ConcurrentHashMap在對Key求Hash值的時候進行了兩次Hash,目的是爲了實現Segment均勻分佈。

小結


說了那麼多,針對Map子類的安全性能夠總結以下幾點:

  • HashMap採用鏈地址法解決哈希衝突,多線程訪問哈希表的位置並修改映射關係的時候,後執行的線程會覆蓋先執行線程的修改,因此不是線程安全的
  • Hashtable採用synchronized關鍵字解決了併發訪問的安全性問題可是效率較低
  • ConcurrentHashMap使用了線程鎖分段技術,每次訪問只容許一個線程修改哈希表的映射關係,因此是線程安全的
相關文章
相關標籤/搜索