原帖地址:https://www.jianshu.com/p/4930801e23c8
進行put操作到閾值時,進行擴容的時候會導致死循環
void transfer(Entry[] newTable)
{
Entry[] src = table;
int newCapacity = newTable.length;
//從OldTable將元素一個個拿出來,然後放到NewTable中
for (int j = 0; j < src.length; j++) {
Entry<K,V> e = src[j];
if (e != null) {
src[j] = null;
do {
Entry<K,V> next = e.next;
//計算節點在新的Map中的位置
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
} while (e != null);
}
}
}
假設hash算法就是簡單的用key mod Entry數組的長度
假設hash算法就是簡單的用key mod Entry數組的長度。這裏一定注意e和next的指向,當併發resize()時,這兩個指針對於死鎖產生起着至關重要的作用。根據方法執行情況,原Map中的鏈表元素在新的Map中將順序顛倒,如上圖所示,經過一次resize()後key爲7的節點排在了key爲3的節點之前。
do {
Entry<K,V> next = e.next;
//計算節點在新的Map中的位置
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
} while (e != null);
再次黏貼這段代碼就是強調這個do while循環就是產生死鎖的罪魁禍首。下面模擬死鎖產生的過程。
注意,並非所有情況下都會產生死鎖,這也需要線程之間的默契配合,怎麼講呢,如圖所示:
①此時線程一,e指向key爲3的節點,next指向key爲7的節點。這點很重要,記下來。去執行線程二。
②假設線程二正常執行,結束後的狀態如下:
③此時線程一被喚醒,線程一的工作空間裏,e和next指向的元素依舊是key爲3和7的節點。線程一開始執行。
④目前還沒發生問題,線程一接着工作。把key(7)摘下來,放到newTable[i]的第一個,然後把e和next往下移。
e.next = newTable[i] 導致 key(3).next 指向了 key(7)。注意:此時的key(7).next 已經指向了key(3), 環形鏈表就這樣出現了。
*轉載者總結:
根本原因是JDK1.8之前的擴容會將結點倒序
其實也就是併發時,將舊數組的數據移到新數組對應位置時,該位置上不能有舊數組上的數據,否則就會形成環。
JDK1.8的解決(四個指針)
通過兩個指針loHead/loTail指向重hash後位置不變的鏈表頭和尾
以及hiHead/hiTail指向重hash後位置+oldCap的鏈表頭和尾
來避免倒序的問題,從而解決死循環的問題。
但是HashMap還是有併發問題,所以還是要用ConcurrentHashMap