【Java】JDK1.8之前HashMap併發情況爲什麼會發生死循環

原帖地址: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數組的長度

transfer()過程

 

 

假設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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章