ConcurrentHashMap 源碼閱讀小結

前言

每一次總結都意味着從新開始,同時也是爲了更好的開始。ConcurrentHashMap 一直是我心中的痛。雖然不敢說徹底讀懂了,但也看了幾個重要的方法,有很多我以爲比較重要的知識點。算法

而後呢,放一些樓主寫的關於 ConcurrentHashMap 相關源碼分析的文章連接:編程

  1. ConcurrentHashMap 擴容分析拾遺
  2. 併發編程——ConcurrentHashMap#addCount() 分析
  3. 併發編程——ConcurrentHashMap#transfer() 擴容逐行分析
  4. 併發編程——ConcurrentHashMap#helpTransfer() 分析
  5. 併發編程 —— ConcurrentHashMap size 方法原理分析
  6. 併發編程之 ConcurrentHashMap(JDK 1.8) putVal 源碼分析
  7. 深刻理解 HashMap put 方法(JDK 8逐行剖析)
  8. 深刻理解 hashcode 和 hash 算法

putVal 方法總結

提及 ConcurrentHashMap ,固然從入口開始說。該方法要點以下:緩存

  1. 不容許有 null key 和 null value。
  2. 只有在第一次 put 的時候才初始化 table。初始化有併發控制。經過 sizeCtl 變量判斷(小於 0)。
  3. 當 hash 對應的下標是 null 時,使用 CAS 插入元素。
  4. 當 hash 對應的下標值是 forward 時,幫助擴容,但有可能幫不了,由於每一個線程默認 16 個桶,若是隻有 16個桶,第二個線程是沒法幫助擴容的。
  5. 若是 hash 衝突了,同步頭節點,進行鏈表操做,若是鏈表長度達到 8 ,分紅紅黑樹。
  6. 調用 addCount 方法,對 size 加一,並判斷是否須要擴容(若是是覆蓋,就不調用該方法)。
  7. Cmap 的併發性能是 hashTable 的 table.length 倍。只有出現鏈表纔會同步,不然使用 CAS 插入。性能極高。

size 方法總結

  1. size 方法不許確,緣由是因爲併發插入,baseCount 難以及時更新。計數盒子也難以及時更新。
  2. 內部經過兩個變量,一個是 baseCount,一個是 counterCells,counterCells 是併發修改 baseCount 後的備用方案。
  3. 具體更新 baseCount 和 counterCells 是在 addCount 方法中。備用方法 fullAddCount 則會死循環插入。
  4. CounterCell 是一個用於分配計數的填充單元,改編自 LongAdder和Striped64。內部只有一個 volatile 的 value 變量,同時這個類標記了 @sun.misc.Contended ,這是一個避免僞共享的註解,用於替代以前的緩存行填充。多線程狀況下,註解讓性能提高 5 倍。

helpTransfer 方法總結

  1. 當 Cmap 嘗試插入的時候,發現該節點是 forward 類型,則會幫助其擴容。
  2. 每次加入一個線程都會將 sizeCtl 的低 16 位加一。同時會校驗高 16 位的標示符。
  3. 擴容最大的幫助線程是 65535,這是低 16 位的最大值限制的。
  4. 每一個線程默認分配 16 個桶,若是桶的數量是 16,那麼第二個線程沒法幫助其擴容。

transfer 方法總結

  1. 該方法會根據 CPU 核心數平均分配給每一個 CPU 相同數量的桶。但若是不夠 16 個,默認就是 16 個。
  2. 擴容是按照 2 倍進行擴容。
  3. 每一個線程在處理完本身領取的區間後,還能夠繼續領取,若是有的話。這個是 transferIndex 變量遞減 16 實現的。
  4. 每次處理空桶的時候,會插入一個 forward 節點,告訴 putVal 的線程:「我正在擴容,快來幫忙」。但若是隻有 16 個桶,只能有一個線程擴容。
  5. 若是有了佔位符,那就不處理,跳過這個桶。
  6. 若是有真正的實際值,那就同步頭節點,防止 putVal 那裏併發。
  7. 同步塊裏會將鏈表拆成兩份,根據 hash & length 獲得是不是 0,若是是0,放在低位,反之,反之放在 length + i 的高位。這裏的設計是爲了防止下次取值的時候,hash 不到正確的位置。
  8. 若是該桶的類型是紅黑樹,也會拆成 2 個,這是必須的。而後判斷拆分過的桶的大小是否小於等於 6,若是是,改爲鏈表。
  9. 線程處理完以後,若是沒有可選區間,且任務沒有完成,就會將整個表檢查一遍,防止遺漏。

addCount 方法總結

  1. 當插入結束的時候,會對 size 進行加一。也會進行是否須要擴容的判斷。
  2. 優先使用計數盒子(若是不是空,說明併發了),若是計數盒子是空,使用 baseCount 變量。對其加 X。
  3. 若是修改 baseCount 失敗,使用計數盒子。若是這次修改失敗,在另外一個方法死循環插入。
  4. 檢查是否須要擴容。
  5. 若是 size 大於等於 sizeCtl 閾值,且長度小於 1 << 30,能夠擴容成 1 << 30,但不能擴容成 1 << 31。
  6. 若是已經在擴容,幫助其擴容,和 helpTransfer 邏輯同樣。
  7. 若是沒有在擴容,自行開啓擴容,更新 sizeCtl 變量爲負數,賦值爲標識符高 16 位 + 2。

小結

ConcurrentHashMap 盡是財富,都是精華代碼,咱們此次閱讀只是管中窺豹,要知道其中包含 53 個類,6300 行代碼,但此次確實收穫不少。有時間必定再次閱讀!! 多線程

能力不高,水平有限,有些地方確實理解不了 Doug Lea 大師的設計,若是有什麼錯誤,還請你們指出。不勝感激。併發

相關文章
相關標籤/搜索