前言
每一次總結都意味着從新開始,同時也是爲了更好的開始。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 大師的設計,若是有什麼錯誤,還請你們指出。不勝感激。併發