談談ConcurrentHashMap的擴容機制

CodeWhisperer001發表於2024-11-12

​ConcurrentHashMap​​​是Java中一種執行緒安全且高效的雜湊表實現,它在Java 8之後的版本中採用了與早期版本不同的擴容機制。在Java 8及以後的版本中,​​ConcurrentHashMap​​利用了分段鎖(Segment,直到Java 8)和之後的CAS(Compare and Swap)操作以及節點的樹化來實現高效的併發讀寫和擴容,下面主要介紹Java 8及之後版本的擴容機制。

Java 8及以後的擴容機制概述

在Java8中,​​ConcurrentHashMap​​摒棄了分段鎖的設計,轉而採用更細粒度的鎖,即每個桶(bin)上的連結串列頭節點作為鎖的粒度,並引入了紅黑樹來最佳化連結串列過長時的查詢效率。其擴容過程主要包括以下幾個步驟:

1.初始化新容量:當需要擴容時,會計算出新的容量,並建立一個新的、容量更大的table。擴容的觸發條件通常是當元素數量超過當前容量與負載因子乘積時。

2.遷移節點:擴容的核心在於將舊table中的元素重新分配到新的table中。這個過程不是一次性完成的,而是隨著每次對map的操作逐步進行。在每個桶上的連結串列頭節點加鎖的情況下,執行緒會遍歷該連結串列上的所有節點,使用新的hash演算法(考慮了新容量)重新計算它們在新table中的位置,並將它們移動過去。對於已經遷移到新table的節點,會設定一個 forwardingNode 標記,指示後續訪問直接跳轉到新table,避免重複遷移。

3.樹化處理:在遷移過程中,如果發現連結串列長度超過了特定閾值(預設為8),會將連結串列轉換成紅黑樹結構,以保持高效的查詢效能。

  1. 更新大小閾值:擴容後,會根據新的容量和負載因子計算新的大小閾值,超過這個閾值才會再次觸發擴容。

特點

  • 併發進行:由於採用了細粒度的鎖,多個執行緒可以同時進行不同桶的元素遷移工作,大大提高了擴容的併發性。
  • 無鎖CAS操作:在某些情況下,比如節點的簡單插入或更新,會盡量使用無鎖的CAS操作來減少鎖的競爭,提高效能。
  • 漸進式:擴容不是一次性完成的,而是隨著併發操作逐步完成,這樣減少了對正常操作的影響。

總結

Java 8及之後版本的​​ConcurrentHashMap​​透過細粒度的鎖、CAS操作、以及紅黑樹的引入,實現了高效且低阻塞的擴容機制,確保了即使在大量併發操作下也能保持良好的效能。這一設計體現了現代併發程式設計中追求的“無鎖”或“少鎖”的設計理念。

1.7版本

  1. 1.7版本的ConcurrentHashMap是基於Segment分段實現的
  2. 每個Segment相對於一個小型的HashMap
  3. 每個Segment內部會進行擴容,和HashMap的擴容邏輯類似 4.先生成新的陣列,然後轉移元素到新陣列中
  4. 擴容的判斷也是每個Segment內部單獨判斷的,判斷是否超過閾值

1.8版本

  1. 1.8版本的ConcurrentHashMap不再基於Segment實現
  2. 當某個執行緒進行put時,如果發現ConcurrentHashMap正在進行擴容那麼該執行緒一起進行擴容 3.如果某個執行緒put時,發現沒有正在進行擴容,則將key-value新增到ConcurrentHashMap中,然後判斷是否超過閾值,超過了則進行擴容
  3. ConcurrentHashMap是支援多個執行緒同時擴容的 5.擴容之前也先生成—個新的陣列 6.在轉移元素時,先將原陣列分組,將每組分給不同的執行緒來進行元素的轉移,每個執行緒負責一組或多組的元素轉移工作

相關文章