【Java】JDK1.8之前HashMap併發情況為什麼會發生死迴圈

TypantK發表於2019-03-13

原帖地址: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

相關文章