HashMap擴容死迴圈問題解析

2022-03-29 08:21:15 字數 2111 閱讀 9230

下面記錄了一下學習過程和自己的理解。

當插入乙個新的鍵值對時,會先根據 key 對 hashmap底層陣列長度取模,得到鍵值對應該存放的陣列下標,然後呼叫 addentry()函式把這個鍵值對插入到這個下標所在的鍊錶中

void addentry(int hash, k key, v value, int

bucketindex)

在這個 addentry() 函式中,會判斷鍵值對個數是否超過了hashmap當前容量的閾值,如果超過了,則說明需要擴容,接下來就呼叫 resize() 函式擴容為原來的兩倍。

void resize(int

newcapacity)

entry newtable = new entry[newcapacity]; //

建立乙個新陣列

transfer(newtable); //

把老陣列中的所有鍵值對都拷貝到新陣列中

table = newtable; //

修改老陣列的指向,把老陣列指向新陣列,完成擴容

threshold = (int)(newcapacity *loadfactor);

}

resize()函式會先建立乙個新陣列,然後呼叫 transfer() 函式把老陣列中的所有鍵值對都拷貝到新陣列中,最後修改老陣列的指向,把老陣列指向新陣列,完成擴容。

擴容過程中會出現迴圈鍊錶的情況就是多個執行緒在執行 transfer() 函式導致的,下面看看 transfer() 函式的**

void

transfer(entry newtable) while (e != null

); }}}

其中最關鍵的就是其中的 do while()迴圈,這裡面就是會發生迴圈鍊錶的**。下面再貼一遍**

do  while (e != null);
現在先走一遍正常擴容的流程,假設有下面這個hashmap, 假設陣列大小為2

現在需要對它進行擴容,擴容後陣列大小為原來的兩倍,建立乙個大小為4的陣列

假設a、b兩個數擴容後剛好又hash衝突了,即又在同乙個鍊錶中,所在下標為3;c在下標為1的鍊錶中。下面開始擴容。

e指標指向了老陣列的第1個鍊錶

至此,老陣列中的健值對已全部拷貝到新陣列中

假設在第 二 次迴圈中的第二步(執行完e.next = newtable[i];)後當前執行緒的時間片剛好用完了,當前執行緒被掛起,這時剛好又有乙個執行緒 p2 也來執行擴容操作,它並不會從第二步開始執行,而是重新從第一步開始執行,加入新執行緒後的擴容圖為

通過解讀hashmap原始碼並結合例項可以發現,hashmap擴容導致死迴圈的主要原因在於擴容過程中使用頭插法將oldtable中的單鏈表中的節點插入到newtable的單鏈表中,所以newtable中的單鏈表會倒置oldtable中的單鏈表。那麼在多個執行緒同時擴容的情況下就可能導致擴容後的hashmap中存在乙個有環的單鏈表,從而導致後續執行get操作的時候,會觸發死迴圈,引起cpu的100%問題。所以一定要避免在併發環境下使用hashmap。

後面還問到除了成環,執行緒不安全還會導致什麼情況發生,也不會,再次gg, 此時,不禁想問:您看我還有機會嗎?

沒機會了,今天官網狀態已經變灰了,徹底涼涼了。這真是個讓人感到悲傷的故事。

cyc2018

多執行緒 HashMap 死迴圈 問題解析

原始碼resize void resize int newcapacity 複製 遷移 void transfer entry newtable while e null 複製 我們可以知道newtable 是新建立的 是執行緒私有的,因為 執行緒1 獲取 e 和next 之後 執行緒2 插入執行了...

HashMap死迴圈問題追蹤

hashmap在設計之初並沒有考慮多執行緒併發的情況,多執行緒併發的情況下理論上應該使用concurrenthashmap,但是程式中經常會無意中在多併發的情況下使用了hashmap,如果是jdk1.8以下的版本,有可能會導致死迴圈,打滿cpu占用,下面基於jdk1.7原始碼分析下原因。我們從put...

HashMap 多執行緒 死迴圈 Java

hashmap,眾所周知,是執行緒不安全的。在多執行緒的情況下,在get 非常有可能出現死迴圈。因為 hashmap採用鍊錶解決hash衝突,因為是鍊錶結構,那麼就很容易形成閉合的鏈路,這樣在迴圈的時候只要有執行緒對這個hashmap進行get操作就會產生死迴圈。只 有乙個執行緒對hashmap的資...