resize導致死迴圈的問題
阿新 • • 發佈:2018-12-20
多執行緒擴容:
這裡我們先把核心程式碼搬出來, 方便檢視
while(null != e) {
Entry<K,V> next = e.next; //第一行
int i = indexFor(e.hash, newCapacity); //第二行
e.next = newTable[i]; //第三行
newTable[i] = e; //第四行
e = next; //第五行
}
去掉了一些冗餘的程式碼, 層次結構更加清晰了。
第一行:記錄odl hash表中e.next
第二行:rehash計算出陣列的位置(hash表中桶的位置)
第三行:e要插入連結串列的頭部, 所以要先將e.next指向new hash表中的第一個元素
第四行:將e放入到new hash表的頭部
第五行: 轉移e到下一個節點, 繼續迴圈下去
核心程式碼如上所說, 下面就是多執行緒同時put的情況了, 然後同時進入transfer方法中:
假設這裡有兩個執行緒同時執行了put()
操作,並進入了transfer()
環節
while(null != e) {
Entry<K,V> next = e.next; //執行緒1執行到這裡被排程掛起了
e.next = newTable[i];
newTable[i] = e;
e = next;
}
那麼現在的狀態為:
從上面的圖我們可以看到,因為執行緒1的 e 指向了 key(3),而 next 指向了 key(7),線上程2 rehash 後,就指向了執行緒2 rehash 後的連結串列。
然後執行緒1被喚醒了:
- 執行
e.next = newTable[i]
,於是 key(3)的 next 指向了執行緒1的新 Hash 表,因為新 Hash 表為空,所以e.next = null
, - 執行
newTable[i] = e
,所以執行緒1的新 Hash 表第一個元素指向了執行緒2新 Hash 表的 key(3)。好了,e 處理完畢。 - 執行
e = next
,將 e 指向 next,所以新的 e 是 key(7)
然後該執行 key(3)的 next 節點 key(7)了:
- 現在的 e 節點是 key(7),首先執行
Entry<K,V> next = e.next
,那麼 next 就是 key(3)了 - 執行
e.next = newTable[i]
,於是key(7) 的 next 就成了 key(3) - 執行
newTable[i] = e
,那麼執行緒1的新 Hash 表第一個元素變成了 key(7) - 執行
e = next
,將 e 指向 next,所以新的 e 是 key(3)
這時候的狀態圖為:
然後又該執行 key(7)的 next 節點 key(3)了:
- 現在的 e 節點是 key(3),首先執行
Entry<K,V> next = e.next
,那麼 next 就是 null - 執行
e.next = newTable[i]
,於是key(3) 的 next 就成了 key(7) - 執行
newTable[i] = e
,那麼執行緒1的新 Hash 表第一個元素變成了 key(3) - 執行
e = next
,將 e 指向 next,所以新的 e 是 key(7)
這時候的狀態如圖所示:
很明顯,環形連結串列出現了!!當然,現在還沒有事情,因為下一個節點是 null,所以transfer()
就完成了,等put()
的其餘過程搞定後,HashMap 的底層實現就是執行緒1的新 Hash 表了。