老生常談,HashMap的死迴圈

2021-09-13 19:42:03 字數 2872 閱讀 9298

最近的幾次面試中,我都問了是否了解hashmap在併發使用時可能發生死迴圈,導致cpu100%,結果讓我很意外,都表示不知道有這樣的問題,讓我意外的是面試者的工作年限都不短。

如果是在單執行緒下使用hashmap,自然是沒有問題的,如果後期由於**優化,這段邏輯引入了多執行緒併發執行,在乙個未知的時間點,會發現cpu占用100%,居高不下,通過檢視堆疊,你會驚訝的發現,執行緒都hang在hashmap的get()方法上,服務重啟之後,問題消失,過段時間可能又復現了。

這是為什麼?

在了解來龍去脈之前,我們先看看hashmap的資料結構。

在內部,hashmap使用乙個entry陣列儲存key、value資料,當一對key、value被加入時,會通過乙個hash演算法得到陣列的下標index,演算法很簡單,根據key的hash值,對陣列的大小取模 hash & (length-1),並把結果插入陣列該位置,如果該位置上已經有元素了,就說明存在hash衝突,這樣會在index位置生成鍊錶。

如果存在hash衝突,最慘的情況,就是所有元素都定位到同乙個位置,形成乙個長長的鍊錶,這樣get乙個值時,最壞情況需要遍歷所有節點,效能變成了o(n),所以元素的hash值演算法和hashmap的初始化大小很重要。

當插入乙個新的節點時,如果不存在相同的key,則會判斷當前內部元素是否已經達到閾值(預設是陣列大小的0.75),如果已經達到閾值,會對陣列進行擴容,也會對鍊錶中的元素進行rehash。

hashmap的put方法實現:

1、判斷key是否已經存在

2、檢查容量是否達到閾值threshold

如果元素個數已經達到閾值,則擴容,並把原來的元素移動過去。

3、擴容實現

這裡會新建乙個更大的陣列,並通過transfer方法,移動元素。

移動的邏輯也很清晰,遍歷原來table中每個位置的鍊錶,並對每個元素進行重新hash,在新的newtable找到歸宿,並插入。

案例分析

假設hashmap初始化大小為4,插入個3節點,不巧的是,這3個節點都hash到同乙個位置,如果按照預設的負載因子的話,插入第3個節點就會擴容,為了驗證效果,假設負載因子是1.

以上是節點移動的相關邏輯。

插入第4個節點時,發生rehash,假設現在有兩個執行緒同時進行,執行緒1和執行緒2,兩個執行緒都會新建新的陣列。

假設 執行緒2 在執行到entrynext = e.next;之後,cpu時間片用完了,這時變數e指向節點a,變數next指向節點b。

執行緒1繼續執行,很不巧,a、b、c節點rehash之後又是在同乙個位置7,開始移動節點

第一步,移動節點a

第二步,移動節點b

注意,這裡的順序是反過來的,繼續移動節點c

這個時候 執行緒1 的時間片用完,內部的table還沒有設定成新的newtable, 執行緒2 開始執行,這時內部的引用關係如下:

這時,在 執行緒2 中,變數e指向節點a,變數next指向節點b,開始執行迴圈體的剩餘邏輯。

執行之後的引用關係如下圖

執行後,變數e指向節點b,因為e不是null,則繼續執行迴圈體,執行後的引用關係

變數e又重新指回節點a,只能繼續執行迴圈體,這裡仔細分析下:

1、執行完entrynext = e.next;,目前節點a沒有next,所以變數next指向null;

2、e.next = newtable[i]; 其中 newtable[i] 指向節點b,那就是把a的next指向了節點b,這樣a和b就相互引用了,形成了乙個環;

3、newtable[i] = e 把節點a放到了陣列i位置;

4、e = next; 把變數e賦值為null,因為第一步中變數next就是指向null;

所以最終的引用關係是這樣的:

節點a和b互相引用,形成了乙個環,當在陣列該位置get尋找對應的key時,就發生了死迴圈。

另外,如果執行緒2把newtable設定成到內部的table,節點c的資料就丟了,看來還有資料遺失的問題。

所以在併發的情況,發生擴容時,可能會產生迴圈鍊錶,在執行get的時候,會觸發死迴圈,引起cpu的100%問題,所以一定要避免在併發環境下使用hashmap。

曾經有人把這個問題報給了sun,不過sun不認為這是乙個bug,因為在hashmap本來就不支援多執行緒使用,要併發就用concurrenthashmap。

老生常談,HashMap的死迴圈

最近的幾次面試中,我都問了是否了解hashmap在併發使用時可能發生死迴圈,導致cpu100 結果讓我很意外,都表示不知道有這樣的問題,讓我意外的是面試者的工作年限都不短。如果是在單執行緒下使用hashmap,自然是沒有問題的,如果後期由於 優化,這段邏輯引入了多執行緒併發執行,在乙個未知的時間點,...

聖杯布局,老生常談

聖杯布局的定義應該是乙個header,乙個footer,中間是container,包含乙個自適應寬度的center,center左邊和右邊各有乙個寬度確定的div,大概是這樣 1 使用普通的css來表現 首先是基本的html header middle left right footer 然後我們簡...

老生常談session,cookie的區別,安全性

一,為什麼session,cookie經常會有人提到 做web開發的人基本上都會用session和cookie,但是僅僅只是會用,並不知道session和cookie的真正的工作原理,都只是憑著感覺來猜測。web開發者只要利用它們來完成工作就行了,所以每個人的理解基本都會有大同小異,我想這就是ses...