HashMap的死迴圈解析
阿新 • • 發佈:2018-11-02
HashMap死迴圈問題圖解
在HashMap的陣列真實長度達到閾值後,會呼叫擴容方法:
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return ;
}
Entry[] newTable = new Entry[newCapacity];
transfer(newTable, initHashSeedAsNeeded(newCapacity));
table = newTable;
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
這裡可以看到如果有兩個執行緒A和B,那麼在呼叫transfer方法之前會在各自的執行緒中建立新的陣列,然後進入到transfer方法中將節點轉移,再看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; ------(1)
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;
}
}
}
我在上面的程式中標記了一個(1),待會會用到,首先假設HashMap中的結構是這樣的:
那麼執行緒A如果執行到(1)的位置,那麼e為節點5,next為節點6,這個時候執行緒B開始執行,在自己的擴容數組裡面執行:
e.next = newTable[i];
newTable[i] = e;
這個時候結構圖:
然後e=next;
在進入迴圈執行:
e.next = newTable[i];
newTable[i] = e;
這個時候結構為:
然後執行緒B執行完畢。執行緒A開始從(1)後面繼續執行,這個時候也是先執行
e.next = newTable[i];
newTable[i] = e;
然後e=next;這個時候e是節點6,然後再進入迴圈,執行上面兩行程式後的結構如下:
但是這個時候由於6的next是有值的,是節點5,所以再執行e=next;的時候,e不為空,還會進入一次迴圈,在執行將節點插入頭部的操作,所以這個時候的結構圖:
可以,看到已經成為了環狀連結串列,當執行get操作的時候就會產生死迴圈。