什么情況會觸發擴容
當往hashMap中成功插入一個key/value節點時,有可能觸發擴容動作:
1、如果新增節點之后,所在鏈表的元素個數達到了閾值 8,則會調用treeifyBin
方法把鏈表轉換成紅黑樹,不過在結構轉換之前,會對數組長度進行判斷,實現如下:

如果數組長度n小於閾值MIN_TREEIFY_CAPACITY
,默認是64,則會調用tryPresize
方法把數組長度擴大到原來的兩倍,並觸發transfer
方法,重新調整節點的位置。

2、新增節點之后,會調用addCount
方法記錄元素個數,並檢查是否需要進行擴容,當數組元素個數達到閾值時,會觸發transfer
方法,重新調整節點的位置。

transfer實現
transfer
方法實現了在並發的情況下,高效的從原始組數往新數組中移動元素,假設擴容之前節點的分布如下,這里區分藍色節點和紅色節點,是為了后續更好的分析:

在上圖中,第14個槽位插入新節點之后,鏈表元素個數已經達到了8,且數組長度為16,優先通過擴容來緩解鏈表過長的問題,實現如下:
1、根據當前數組長度n,新建一個兩倍長度的數組nextTable
;

2、初始化ForwardingNode
節點,其中保存了新數組nextTable
的引用,在處理完每個槽位的節點之后當做占位節點,表示該槽位已經處理過了;

3、通過for
自循環處理每個槽位中的鏈表元素,默認advace
為真,通過CAS設置transferIndex
屬性值,並初始化i
和bound
值,i
指當前處理的槽位序號,bound
指需要處理的槽位邊界,先處理槽位15的節點;

4、在當前假設條件下,槽位15中沒有節點,則通過CAS插入在第二步中初始化的ForwardingNode
節點,用於告訴其它線程該槽位已經處理過了;

5、如果槽位15已經被線程A處理了,那么線程B處理到這個節點時,取到該節點的hash值應該為MOVED
,值為-1
,則直接跳過,繼續處理下一個槽位14的節點;

6、處理槽位14的節點,是一個鏈表結構,先定義兩個變量節點ln
和hn
,按我的理解應該是lowNode
和highNode
,分別保存hash值的第X位為0和1的節點,具體實現如下:

使用fn&n
可以快速把鏈表中的元素區分成兩類,A類是hash值的第X位為0,B類是hash值的第X位為1,並通過lastRun
記錄最后需要處理的節點,A類和B類節點可以分散到新數組的槽位14和30中,在原數組的槽位14中,藍色節點第X為0,紅色節點第X為1,把鏈表拉平顯示如下:

1、通過遍歷鏈表,記錄runBit
和lastRun
,分別為1和節點6,所以設置hn
為節點6,ln
為null;
2、重新遍歷鏈表,以lastRun
節點為終止條件,根據第X位的值分別構造ln鏈表和hn鏈表:
ln鏈:和原來鏈表相比,順序已經不一樣了

hn鏈:

通過CAS把ln鏈表設置到新數組的i位置,hn鏈表設置到i+n的位置;
7、如果該槽位是紅黑樹結構,則構造樹節點lo
和hi
,遍歷紅黑樹中的節點,同樣根據hash&n
算法,把節點分為兩類,分別插入到lo
和hi
為頭的鏈表中,根據lo
和hi
鏈表中的元素個數分別生成ln
和hn
節點,其中ln
節點的生成邏輯如下:
(1)如果lo
鏈表的元素個數小於等於UNTREEIFY_THRESHOLD
,默認為6,則通過untreeify
方法把樹節點鏈表轉化成普通節點鏈表;
(2)否則判斷hi
鏈表中的元素個數是否等於0:如果等於0,表示lo
鏈表中包含了所有原始節點,則設置原始紅黑樹給ln
,否則根據lo
鏈表重新構造紅黑樹。

最后,同樣的通過CAS把ln
設置到新數組的i
位置,hn
設置到i+n
位置。