師傅,我經常聽別人說,不要在併發狀況下使用HashMap,可能會出現死循環,這個死循環是怎麼造成的呢?面試
一塵數組
慧能數據結構
這個聽爲師慢慢道來併發
咱們都知道,HashMap的底層是由數組加鏈表來實現的ide
當往HashMap中put一個鍵值對時,若是HashMap中的鍵值對數量 size 大於或等於閾值(threshold) 而且null != table[bucketIndex] 時會進行擴容高併發
bucketIndex爲該鍵值對最後被散列到hash表table的位置學習
好比 table的初始容量爲4,加載因子爲0.75,此時閾值爲3,table已有三個元素,如今put一個元素<1,」A」>,<1,」A」>被散列到table[1]處,而table[1] != null,此時知足擴容條件線程
閾值 = 容量 * 加載因子3d
threshold = capacity * loadFactorblog
擴容時先生成一個是table大小2倍的newTable,而後把table中的元素遷移到newTable中,遷移的工做就由 transfer方法來完成,這個方法就是引發死循環的緣由所在
如今咱們來模擬一下環是如何造成的,設HashMap的初始容量爲4,先往HashMap中依次put三個元素<5,」B」>、<9,」C」>、<1,」A」>,它們都被散列到table[1]處,造成了鏈
假設此時有兩個線程併發對該HashMap進行put,線程1 put <13,」D」>時,這個鍵值對被散列到table[1]處,知足擴容條件,進行擴容(生成newTable並進行遷移)
此時,線程1用 transfer 方法將 table中的元素遷移到 newTable 中,假設線程1執行完 transfer 方法中的 Entry<K,V> next = e.next 語句後時間片用完,掛起
此時,線程1內的 e 指向 <1,「A」>,next指向<9,」C」>
而後線程2 put <16,」E」>,一樣這個鍵值對被散列到table[1]處,知足擴容條件,進行擴容
生成完newTable後用transfer方法進行遷移,執行完Entry<K,V> next = e.next;後與線程1同樣,e指向<1,」A」>,next指向<9,」C」>
線程2 而後執行循環體下面的語句
線程2執行完以後爲下圖
而後線程2按照一樣的邏輯進行第二次循環(while循環),下面是第二遍循環後的結果
下面是第三遍循環後的結果
此時,線程2時間片用完,線程1獲得時間片,開始執行
注意,此時線程1的e指向<1,A>,next指向<9,C>,在線程2操做鏈表的時候,線程1 中的e,next指向的元素沒變(一直是紅色的e和next)
線程一和線程二總體圖爲:
而後線程1開始執行循環內剩下的代碼
執行完後爲下圖
線程1繼續執行第二遍循環
執行完爲下圖
線程1繼續第三遍循環(注意:這次循環會造成環)
先執行 Entry<K,V> next = <1,A>.next
而後執行 <1,A>.next = newTable[1]
此時環已經造成
而後執行剩餘的兩行代碼
執行完,e 爲 null,退出循環
當造成環後,當給HashMap中put元素的時候,這個元素恰巧落在了table[1](無論有沒有更新table),而這個元素的Key不在table[1]這條鏈上,此時會發生死循環
或者在get元素的時候,該元素恰巧落在table[1]上,而且該元素的Key不在該鏈上,會發生死循環
原來死循環是這樣造成的
一塵
慧能
對,核心就是線程2修改了原來的鏈表結構,而線程1卻以原來的邏輯執行遷移
那併發下我還想使用散列表這種數據結構怎麼辦呢
一塵
慧能
用ConcurrentHashMap
今日份讀者福利:轉發+關注公衆號:麒麟改bug,獲取小編整理好的200多頁Java核心學習筆記一份!
喜歡小編今日的分享,記得關注我點贊喲,感謝支持!重要的事情說三遍,轉發+轉發+轉發,必定要記得轉發 關注哦!!!