文章目錄
前言
此乃隨筆, 用於記錄ConcurrentHashMap的擴容邏輯, 這位大神 JDK1.8–深度分析CONCURRENTHASHMAP原理分析 的文章給了我莫大的鼓勵。
順帶, 記錄了HashMap的擴容細則。
至今爲止, 我依舊沒懂 ConcurrentHashMap 的擴容細則。
ConcurrentHashMap resize/rehash
1. 什麼時候resize
跟HashMap一樣, 使用
Node數組
, 數組有大小Capacity
;
數組元素上掛Node結點, 可以是單鏈表(不過8位), 可以是紅黑樹。
.
因爲是併發HashMap, 沒有size
字段(即時統計)。
加載因子loadFactor
, 1.8已經不再使用。
- 每個Node結點的長度
> 8
時;- 如果數組大小
< 64
, 優先擴容resize(rehash)
- 大於64,優先轉紅黑樹。轉時鎖邏輯與
put
鎖邏輯一致。
- 如果數組大小
- ConcurrentHashMap元素個數(
size
)>
sizeCtl
(0.75 * tab.length
)- 每次
resize(rehash)
我完畢之後會一直檢查是否需要再次resize(rehash)
- 源碼判斷邏輯:
s >= (long)(sc = sizeCtl)
(其中s=ConcurrentHashMap.size()
) - 源碼判斷邏輯:sizeCtl = (n << 1) - (n >>> 1); (其中n=resize前的size)。
- 0.75:
2n - 0.5n = 1.5n
- resize後的大小
2n
1.5n / 2n = 0.75
。
- 0.75:
- 每次
2. 怎麼resize
這塊沒看透。看透再補充
.
ConcurrentHashMap
始終維護一個Node[] table
表示當前的數據, 以及一個Node[] newTab
表示將要resize去的表。
- 多線程
resize(rehash)
1. 每個線程負責16個Node, 最多允許 數組有大小Capacity
/16
個線程。
2. 正在執行put的那個線程,也可能參與resize(rehash)
3. 參與resize(rehash)
, 僅在當前key對應的Node
結點已經被轉移,否則加鎖繼續put。 - resize是把舊
Node
轉移到一個新的Node
數組(源碼2410
行);
1. resieze結束的標記(邏輯非常複雜,沒懂,參閱 JDK1.8–深度分析CONCURRENTHASHMAP原理分析 的sizeCtl擴容退出機制章節), 或者直接看源碼2414
行。
2. 每一個被轉移完畢的Node
結點的hash = -1
(ForwardingNode
); rehash
的方式和HashMap
的實現方式一致
1. 如果是鏈表, 把鏈表的每個結點rehash到新的table中。
2. 如果是紅黑樹, 也是一樣的執行邏輯。
關於加鎖退出機制, 以及Node結點長度大於8時, 以及創建table時, 邏輯相似。 都是
resize(rehash)
的過程。
HashMap resize/rehash
這個相對來說, 單線程, 就比較好理解了、當
size
>table.length * 0.75
時resize/rehash
執行。
- resize 直接將 舊 table 賦值爲一個
2倍大小
的新table
1. 因此resize的時候可能導致get得到null
(實際有值)。(多線程環境)
2. 源碼:Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
&&table = newTab;
- 迭代每個數組元素
Node
, 對鏈表和紅黑樹遷移。 - 單節點直接遷移(
next
= null)
1. 數組位置算法:(table.length - 1) & hash
2. table.length 爲2^n, 則(table.length - 1)
全都是二進制的1
;
3. 擴容前後的值都是hash的值, 之前爲null, 之後也還是爲null. 可以直接賦值。 - 鏈表和紅黑樹分別rehash到新的table上去。
Redis resize/rehash
據說是 漸進式 rehash,https://www.cnblogs.com/meituantech/p/9376472.html 看不懂。