ConcurrentHashMap的擴容機制(jdk1.8)(轉)


什么情況會觸發擴容

當往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屬性值,並初始化ibound值,i指當前處理的槽位序號,bound指需要處理的槽位邊界,先處理槽位15的節點;

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

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

6、處理槽位14的節點,是一個鏈表結構,先定義兩個變量節點lnhn,按我的理解應該是lowNodehighNode,分別保存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、通過遍歷鏈表,記錄runBitlastRun,分別為1和節點6,所以設置hn為節點6,ln為null;
2、重新遍歷鏈表,以lastRun節點為終止條件,根據第X位的值分別構造ln鏈表和hn鏈表:

ln鏈:和原來鏈表相比,順序已經不一樣了

hn鏈:

通過CAS把ln鏈表設置到新數組的i位置,hn鏈表設置到i+n的位置;

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

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM