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()以及其餘對外暴露的方法都會有這個風險,這算jvm的bug嗎?應該說不是的,這個現象很早之前就報告出來了(詳細見: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6423457)。Sun的工程師並不認爲這 是bug,而是建議在這樣的場景下應用」ConcurrentHashMap」,在構建可擴展的系統時應將這點 歸入規範中。數據結構

  這篇翻譯提到了對HashMap的誤用,但它沒有點破HashMap內部結構在什麼樣誤用狀況下怎麼被 破壞的;我想要一個有力的場景來弄清楚。再從李鵬的blog來看,用了2個線程來put就模擬出來了,最後堆棧是在 transfer 方法上(該方法是數據擴容時將數據從舊容器轉移到新容器)。 仔細分析了一下里面的代碼,基本得出了緣由,證實了我以前的推測。併發

假設擴容時的一個場景以下(右邊的容器是一個長度 2 倍於當前容器的數組) 單線程狀況。jvm

咱們分析數據轉移的過程,主要是鏈表的轉移spa

執行過一次後的狀態:.net

最終的結果:線程

兩個線程併發狀況下,擴容時可能會建立出 2 個新數組容器翻譯

順利的話,最終轉移完多是這樣的結果3d

但併發狀況下,出現死循環的可能場景是什麼呢? 還要詳細的分析一下代碼,下面的代碼中重點在 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

相關文章
相關標籤/搜索