1. 程式人生 > 實用技巧 >JDK1.7 hashMap併發擴容死迴圈原理

JDK1.7 hashMap併發擴容死迴圈原理

JDK 1.7擴容的實現程式碼

void resize(int newCapacity) {
    Entry[] oldTable = table;
    int oldCapacity = oldTable.length;
    ...

    Entry[] newTable = new Entry[newCapacity];
    ...
    transfer(newTable, rehash);
    table = newTable;
    threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}

即建立一個更大的陣列,通過transfer方法,移動元素

void transfer(Entry[] newTable, boolean rehash) {
    int newCapacity = newTable.length;
    for (Entry<K,V> e : table) {
        while(null != e) {
            Entry<K,V> next = e.next;   ----執行緒B執行到這裡掛起(未執行)
            if (rehash) {
                e.hash = null == e.key ? 0 : hash(e.key);
            }
            int i = indexFor(e.hash, newCapacity);
            e.next = newTable[i];
            newTable[i] = e;
            e = next;
        }
    }
}
遍歷陣列中每個位置的連結串列,對每個元素進行重新hash(rehash = true時),在新的newTable以頭插法的方式插入。

假設有一個hashMap陣列(正常是2的N次長度,這裡方便舉例), 節點3上存有abc元素,此時發生擴容



此時假設有兩個執行緒

執行緒B在執行到Entry<K,V> next = e.next;後掛起,此時e指向元素a,e.next指向元素b

到執行緒A在new table的陣列7位置依次用頭插法插入3個元素後

此時執行緒B繼續執行以下程式碼

Entry<K,V> next = e.next;   //next = b
 e.next = newTable[i];    //
陣列7的地址賦予變數e.next
newTable[i] = e; //將a放到陣列7的位置
e = next;  //  e = next = b
執行結束的關係如圖

變數e = b不是null,迴圈繼續執行,

Entry<K,V> next = e.next;  //  next = a
 e.next = newTable[i];  //陣列7地址指向e.next
 newTable[i] = e; //將b放到陣列7的位置
 e = next;    //e =next = a

執行後引用關係圖

此時變數e = a仍舊不為空,繼續迴圈。。

Entry<K,V> next = e.next;  //  變數a沒有next,所以next = null
 e.next = newTable[i];  // 因為newTable[i]存的是b,這一步相當於將a的next指向了b,於是問題出現了
newTable[i] = e; //將變數a放到陣列7的位置
e
= next; // e= next = null



當在陣列7遍歷節點尋找對應的key時, 節點a和b就發生了死迴圈, 直到cpu被完全耗盡。

該問題在JDK1.8修復了,併發還是用concurrent hashmap吧

本文圖示來自https://www.jianshu.com/p/1e9cf0ac07f4