HashMap導致死循環問題
雖然我推測是鏈表形成閉環,但 沒有去證明過。從網上找了一下: http://blog.csdn.net/autoinspired/archive/2008/07/16/2662290.aspx 裏面也有提到:
產生這個死循環的根源在於對一個未保護的共享變量 — 一個”HashMap”數據結構的操作。當在 所有操作的方法上加了”synchronized”後,一切恢復了正常。檢查”HashMap”(Java SE 5.0)的源 碼,我們發現有潛在的破壞其內部結構最終造成死循環的可能。在下面的代碼中,如果我們使得 HashMap中的entries進入循環,那 麽”e.next()”永遠都不會為null。
不僅get()方法會這樣,put()以及其他對外暴露的方法都會有這個風險
這篇翻譯提到了對HashMap
的誤用,但它沒有點破HashMap
內部結構在什麽樣誤用情況下怎麽被 破壞的;我想要一個有力的場景來弄清楚。再從李鵬的blog來看,用了2個線程來put就模擬出來了,最後堆棧是在 transfer
方法上(該方法是數據擴容時將數據從舊容器轉移到新容器)
假設擴容時的一個場景如下(右邊的容器是一個長度 2 倍於當前容器的數組) 單線程情況。
我們分析數據轉移的過程,主要是鏈表的轉移
執行過一次後的狀態:
最終的結果:
兩個線程並發情況下,擴容時可能會創建出 2 個新數組容器
順利的話,最終轉移完可能是這樣的結果
但並發情況下,出現死循環的可能場景是什麽呢? 還要詳細的分析一下代碼,下面的代碼中重點在 do/while
循環結構中(完成鏈 表的轉移)。
1 // 擴容操作,從一個數組轉移到另一個數組 2 void transfer(Entry[] newTable) {3 Entry[] src = table; 4 int newCapacity = newTable.length; 5 for (int j = 0; j < src.length; j++) { 6 Entry<K,V> e = src[j]; 7 if (e != null) { 8 src[j] = null; 9 do { 10 Entry<K,V> next = e.next; //假設第一個線程執行到這裏 11 int i = indexFor(e.hash, newCapacity); 12 e.next = newTable[i]; 13 newTable[i] = e; 14 e = next; 15 } while (e != null); // 可能導致死循環 16 } 17 } 18 }
2 個線程並發情況下, 當線程 1 執行到上面第 9 行時,而線程 2 已經完成了一 輪 do/while 操作,那麽它的狀態如下圖:
(上面的數組時線程 1 的,已經完成了鏈表數據的轉移;下面的是線程 2 的,它 即將開始進行對鏈表數據的轉移,此時它記錄 E1 和 E2 的首位已經被線程 1 翻 轉了)
後續的步驟如下:
1) 插入 E1 節點,E1 節點的 next 指向新容器索引位置上的值(null 或 entry)
2) 插入 E2 節點,E2 的 next 指向當前索引位置上的引用值 E1
3)因為 next 不為 null,鏈表繼續移動,此時 2 節點之間形成了閉環。造成了 死循環。
上面只是一種情況,造成單線程死循環,雙核 cpu 的話占用率是 50%,還有導致 100%的情況,應該也都是鏈表的閉環所致。
最終,這並不是 HashMap 的問題,是使用場景的不當,在並發情況下選擇非線程 安全的容器是沒有保障的。
轉自:https://blog.csdn.net/zhao9tian/article/details/38976933
HashMap導致死循環問題