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),會將連結串列轉換成紅黑樹結構,以保持高效的查詢效能。
- 更新大小閾值:擴容後,會根據新的容量和負載因子計算新的大小閾值,超過這個閾值才會再次觸發擴容。
特點
- 併發進行:由於採用了細粒度的鎖,多個執行緒可以同時進行不同桶的元素遷移工作,大大提高了擴容的併發性。
- 無鎖CAS操作:在某些情況下,比如節點的簡單插入或更新,會盡量使用無鎖的CAS操作來減少鎖的競爭,提高效能。
- 漸進式:擴容不是一次性完成的,而是隨著併發操作逐步完成,這樣減少了對正常操作的影響。
總結
Java 8及之後版本的ConcurrentHashMap
透過細粒度的鎖、CAS操作、以及紅黑樹的引入,實現了高效且低阻塞的擴容機制,確保了即使在大量併發操作下也能保持良好的效能。這一設計體現了現代併發程式設計中追求的“無鎖”或“少鎖”的設計理念。
1.7版本
- 1.7版本的ConcurrentHashMap是基於Segment分段實現的
- 每個Segment相對於一個小型的HashMap
- 每個Segment內部會進行擴容,和HashMap的擴容邏輯類似 4.先生成新的陣列,然後轉移元素到新陣列中
- 擴容的判斷也是每個Segment內部單獨判斷的,判斷是否超過閾值
1.8版本
- 1.8版本的ConcurrentHashMap不再基於Segment實現
- 當某個執行緒進行put時,如果發現ConcurrentHashMap正在進行擴容那麼該執行緒一起進行擴容 3.如果某個執行緒put時,發現沒有正在進行擴容,則將key-value新增到ConcurrentHashMap中,然後判斷是否超過閾值,超過了則進行擴容
- ConcurrentHashMap是支援多個執行緒同時擴容的 5.擴容之前也先生成—個新的陣列 6.在轉移元素時,先將原陣列分組,將每組分給不同的執行緒來進行元素的轉移,每個執行緒負責一組或多組的元素轉移工作