ConcurrentHashMap原始碼解讀

2022-09-19 04:36:15 字數 2620 閱讀 5279

曾經研究過jkd1.5新特性,其中concurrenthashmap就是其中之一,其特點:效率比hashtable高,併發性比hashmap好。結合了兩者的特點。

集合是程式設計中最常用的資料結構。而談到併發,幾乎總是離不開集合這類高階資料結構的支援。比如兩個執行緒需要同時訪問乙個中間臨界區(queue),比如常會用快取作為外部檔案的副本(hashmap)。這篇文章主要分析jdk1.5的3種併發集合型別(concurrent,copyonright,queue)中的concurrenthashmap,讓我們從原理上細緻的了解它們,能夠讓我們在深度專案開發中獲益非淺。

在tiger之前,我們使用得最多的資料結構之一就是hashmap和hashtable。大家都知道,hashmap中未進行同步考慮,而hashtable則使用了synchronized,帶來的直接影響就是可選擇,我們可以在單執行緒時使用hashmap提高效率,而多執行緒時用hashtable來保證安全。

當我們享受著jdk帶來的便利時同樣承受它帶來的不幸惡果。通過分析hashtable就知道, synchronized是針對整張hash表的,即每次鎖住整張表讓執行緒獨佔,安全的背後是巨大的浪費,慧眼獨具的douglee立馬拿出了解決方案----concurrenthashmap。

concurrenthashmap和hashtable主要區別就是圍繞著鎖的粒度以及如何鎖。如圖

左邊便是hashtable的實現方式---鎖整個hash表;而右邊則是concurrenthashmap的實現方式---鎖桶(或段)。concurrenthashmap將hash表分為16個桶(預設值),諸如get,put,remove等常用操作只鎖當前需要用到的桶。試想,原來 只能乙個執行緒進入,現在卻能同時16個寫執行緒進入(寫執行緒才需要鎖定,而讀執行緒幾乎不受限制,之後會提到),併發性的提公升是顯而易見的。

更令人驚訝的是concurrenthashmap的讀取併發,因為在讀取的大多數時候都沒有用到鎖定,所以讀取操作幾乎是完全的併發操作,而寫操作鎖定的粒度又非常細,比起之前又更加快速(這一點在桶更多時表現得更明顯些)。只有在求size等操作時才需要鎖定整個表。而在迭代時,concurrenthashmap使用了不同於傳統集合的快速失敗迭代器(見之前的文章《j**a api備忘---集合》)的另一種迭代方式,我們稱為弱一致迭代器。在這種迭代方式中,當iterator被建立後集合再發生改變就不再是丟擲 concurrentmodificationexception,取而代之的是在改變時new新的資料從而不影響原有的數 據,iterator完成後再將頭指標替換為新的資料,這樣iterator執行緒可以使用原來老的資料,而寫執行緒也可以併發的完成改變,更重要的,這保證 了多個執行緒併發執行的連續性和擴充套件性,是效能提公升的關鍵。

接下來,讓我們看看concurrenthashmap中的幾個重要方法,心裡知道了實現機制後,使用起來就更加有底氣。

concurrenthashmap中主要實體類就是三個:concurrenthashmap(整個hash表),segment(桶),hashentry(節點),對應上面的圖可以看出之間的關係。

get 方法(請注意,這裡分析的方法都是針對桶的,因為concurrenthashmap的最大改進就是將粒度細化到了桶上),首先判斷了當前桶的資料個數是 否為0,為0自然不可能get到什麼,只有返回null,這樣做避免了不必要的搜尋,也用最小的代價避免出錯。然後得到頭節點(方法將在下面涉及)之後就 是根據hash和key逐個判斷是否是指定的值,如果是並且值非空就說明找到了,直接返回;程式非常簡單,但有乙個令人困惑的地方,這句return readvalueunderlock(e)到底是用來幹什麼的呢?研究它的**,在鎖定之後返回乙個值。但這裡已經有一句v v = e.value得到了節點的值,這句return readvalueunderlock(e)是否多此一舉?事實上,這裡完全是為了併發考慮的,這裡當v為空時,可能是乙個執行緒正在改變節點,而之前的 get操作都未進行鎖定,根據bernstein條件,讀後寫或寫後讀都會引起資料的不一致,所以這裡要對這個e重新上鎖再讀一遍,以保證得到的是正確值,這裡不得不佩服doug lee思維的嚴密性。整個get操作只有很少的情況會鎖定,相對於之前的hashtable,併發是不可避免的啊!

總結:1、concurrenthashmap結合了hashtable與hashmap的優點,實現分段鎖,使用的鎖是reentrantlock(重入鎖)

2、concurrenthashmap 讀操作不進行加鎖

3、 concurrenthashmap 擴容時不對整個容器擴容,只對segment進行擴容

4、統計map長度時會對整個map的增刪改操作進行加鎖,所以此時效能會比較低。建議在迴圈時先拿到長度,不會每次都去重新拿一下

j**a知識體系總結(2021版)

超詳細的springboot學習筆記

j**a多執行緒基礎知識總結(絕對經典)

j**a面試題總結(附答案)

vue基礎知識總結(絕對經典)

常見資料結構與演算法整理總結

ConcurrentHashMap原始碼分析

hashmap 先說hashmap,hashmap是執行緒不安全 的,在併發環境下,可能會形成環狀鍊錶 hashtable hashtable和hashmap的實現原理幾乎一樣,差別無非是1.hashtable不允許key和value為null 2.hashtable是執行緒安全的。但是hashta...

ConcurrentHashMap原始碼詳解

成員變數private static final int maximum capacity 1 30 private static final int default capacity 16 static final int max array size integer.max value 8 pr...

concurrentHashMap原始碼分析

concurrenthashmap是hashmap的執行緒安全版本,內部也是使用 陣列 鍊錶 紅黑樹 的結構來儲存元素。相比於同樣執行緒安全的hashtable來說,效率等各方面都有極大地提高。在這裡可以使用上篇那個 進行測試,根據結果可以知道concurrenthashmap是執行緒安全的,由於分...