ConcurrentHashMap 源碼閱讀小結
阿新 • • 發佈:2018-05-20
初始化 transfer 方法總結 最大 jdk 可能 help 之前 striped
前言
每一次總結都意味著重新開始,同時也是為了更好的開始。ConcurrentHashMap 一直是我心中的痛。雖然不敢說完全讀懂了,但也看了幾個重要的方法,有不少我覺得比較重要的知識點。
然後呢,放一些樓主寫的關於 ConcurrentHashMap 相關源碼分析的文章鏈接:
- ConcurrentHashMap 擴容分析拾遺
- 並發編程——ConcurrentHashMap#addCount() 分析
- 並發編程——ConcurrentHashMap#transfer() 擴容逐行分析
- 並發編程——ConcurrentHashMap#helpTransfer() 分析
- 並發編程 —— ConcurrentHashMap size 方法原理分析
- 並發編程之 ConcurrentHashMap(JDK 1.8) putVal 源碼分析
- 深入理解 HashMap put 方法(JDK 8逐行剖析)
- 深入理解 hashcode 和 hash 算法
putVal 方法總結
說起 ConcurrentHashMap ,當然從入口開始說。該方法要點如下:
- 不允許有 null key 和 null value。
- 只有在第一次 put 的時候才初始化 table。初始化有並發控制。通過 sizeCtl 變量判斷(小於 0)。
- 當 hash 對應的下標是 null 時,使用 CAS 插入元素。
- 當 hash 對應的下標值是 forward 時,幫助擴容,但有可能幫不了,因為每個線程默認 16 個桶,如果只有 16個桶,第二個線程是無法幫助擴容的。
- 如果 hash 沖突了,同步頭節點,進行鏈表操作,如果鏈表長度達到 8 ,分成紅黑樹。
- 調用 addCount 方法,對 size 加一,並判斷是否需要擴容(如果是覆蓋,就不調用該方法)。
- Cmap 的並發性能是 hashTable 的 table.length 倍。只有出現鏈表才會同步,否則使用 CAS 插入。性能極高。
size 方法總結
- size 方法不準確,原因是由於並發插入,baseCount 難以及時更新。計數盒子也難以及時更新。
- 內部通過兩個變量,一個是 baseCount,一個是 counterCells,counterCells 是並發修改 baseCount 後的備用方案。
- 具體更新 baseCount 和 counterCells 是在 addCount 方法中。備用方法 fullAddCount 則會死循環插入。
- CounterCell 是一個用於分配計數的填充單元,改編自 LongAdder和Striped64。內部只有一個 volatile 的 value 變量,同時這個類標記了
@sun.misc.Contended
,這是一個避免偽共享的註解,用於替代之前的緩存行填充。多線程情況下,註解讓性能提升 5 倍。
helpTransfer 方法總結
- 當 Cmap 嘗試插入的時候,發現該節點是 forward 類型,則會幫助其擴容。
- 每次加入一個線程都會將 sizeCtl 的低 16 位加一。同時會校驗高 16 位的標示符。
- 擴容最大的幫助線程是 65535,這是低 16 位的最大值限制的。
- 每個線程默認分配 16 個桶,如果桶的數量是 16,那麽第二個線程無法幫助其擴容。
transfer 方法總結
- 該方法會根據 CPU 核心數平均分配給每個 CPU 相同數量的桶。但如果不夠 16 個,默認就是 16 個。
- 擴容是按照 2 倍進行擴容。
- 每個線程在處理完自己領取的區間後,還可以繼續領取,如果有的話。這個是 transferIndex 變量遞減 16 實現的。
- 每次處理空桶的時候,會插入一個 forward 節點,告訴 putVal 的線程:“我正在擴容,快來幫忙”。但如果只有 16 個桶,只能有一個線程擴容。
- 如果有了占位符,那就不處理,跳過這個桶。
- 如果有真正的實際值,那就同步頭節點,防止 putVal 那裏並發。
- 同步塊裏會將鏈表拆成兩份,根據 hash & length 得到是否是 0,如果是0,放在低位,反之,反之放在 length + i 的高位。這裏的設計是為了防止下次取值的時候,hash 不到正確的位置。
- 如果該桶的類型是紅黑樹,也會拆成 2 個,這是必須的。然後判斷拆分過的桶的大小是否小於等於 6,如果是,改成鏈表。
- 線程處理完之後,如果沒有可選區間,且任務沒有完成,就會將整個表檢查一遍,防止遺漏。
addCount 方法總結
- 當插入結束的時候,會對 size 進行加一。也會進行是否需要擴容的判斷。
- 優先使用計數盒子(如果不是空,說明並發了),如果計數盒子是空,使用 baseCount 變量。對其加 X。
- 如果修改 baseCount 失敗,使用計數盒子。如果此次修改失敗,在另一個方法死循環插入。
- 檢查是否需要擴容。
- 如果 size 大於等於 sizeCtl 閾值,且長度小於 1 << 30,可以擴容成 1 << 30,但不能擴容成 1 << 31。
- 如果已經在擴容,幫助其擴容,和 helpTransfer 邏輯一樣。
- 如果沒有在擴容,自行開啟擴容,更新 sizeCtl 變量為負數,賦值為標識符高 16 位 + 2。
小結
ConcurrentHashMap 滿是財富,都是精華代碼,我們這次閱讀只是管中窺豹,要知道其中包含 53 個類,6300 行代碼,但這次確實收獲很多。有時間一定再次閱讀!!
能力不高,水平有限,有些地方確實理解不了 Doug Lea 大師的設計,如果有什麽錯誤,還請大家指出。不勝感激。
ConcurrentHashMap 源碼閱讀小結