下面記錄了一下學習過程和自己的理解。
當插入乙個新的鍵值對時,會先根據 key 對 hashmap底層陣列長度取模,得到鍵值對應該存放的陣列下標,然後呼叫 addentry()函式把這個鍵值對插入到這個下標所在的鍊錶中
void addentry(int hash, k key, v value, int在這個 addentry() 函式中,會判斷鍵值對個數是否超過了hashmap當前容量的閾值,如果超過了,則說明需要擴容,接下來就呼叫 resize() 函式擴容為原來的兩倍。bucketindex)
void resize(intresize()函式會先建立乙個新陣列,然後呼叫 transfer() 函式把老陣列中的所有鍵值對都拷貝到新陣列中,最後修改老陣列的指向,把老陣列指向新陣列,完成擴容。newcapacity)
entry newtable = new entry[newcapacity]; //
建立乙個新陣列
transfer(newtable); //
把老陣列中的所有鍵值對都拷貝到新陣列中
table = newtable; //
修改老陣列的指向,把老陣列指向新陣列,完成擴容
threshold = (int)(newcapacity *loadfactor);
}
擴容過程中會出現迴圈鍊錶的情況就是多個執行緒在執行 transfer() 函式導致的,下面看看 transfer() 函式的**
void其中最關鍵的就是其中的 do while()迴圈,這裡面就是會發生迴圈鍊錶的**。下面再貼一遍**transfer(entry newtable) while (e != null
); }}}
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的資...