深入探究Java中的HashMap為什么會產(chǎn)生死循環(huán)
HashMap 死循環(huán)發(fā)生在 JDK 1.8 之前的版本中,它是指在并發(fā)環(huán)境下,因為多個線程同時進(jìn)行 put 操作,導(dǎo)致鏈表形成環(huán)形數(shù)據(jù)結(jié)構(gòu),一旦形成環(huán)形數(shù)據(jù)結(jié)構(gòu),在 get(key) 的時候就會產(chǎn)生死循環(huán)。如下圖所示:
死循環(huán)原因
HashMap 導(dǎo)致死循環(huán)的原因是由以下條件共同導(dǎo)致的:
- HashMap 使用頭插法進(jìn)行數(shù)據(jù)插入(JDK 1.8 之前);
- 多線程同時添加;
- 觸發(fā)了 HashMap 擴(kuò)容。
什么是頭插法
頭插法是指新來的值會取代原有的值,插入到鏈表的頭部,如下圖所示。
原鏈表如下圖所示:
此時使用頭插入插入一個元素 Z,如下圖所示:
頭插法會導(dǎo)致 HashMap 在進(jìn)行擴(kuò)容時,鏈表的順序發(fā)生反轉(zhuǎn),如下圖所示:
因為在 HashMap 擴(kuò)容時,會先從舊 HashMap 的頭節(jié)點讀取并插入到新 HashMap 節(jié)點中,舊節(jié)點的讀取順序是 A -> B -> C,于是插入到新 HashMap 中的順序就變成了 C -> B -> A,這樣就破壞了鏈表的順序,導(dǎo)致了鏈表反轉(zhuǎn)。
死循環(huán)產(chǎn)生過程
死循環(huán)執(zhí)行步驟1
死循環(huán)是因為并發(fā) HashMap 擴(kuò)容導(dǎo)致的,并發(fā)擴(kuò)容的第一步,線程 T1 和線程 T2 要對 HashMap 進(jìn)行擴(kuò)容操作,此時 T1 和 T2 指向的是鏈表的頭結(jié)點元素 A,而 T1 和 T2 的下一個節(jié)點,也就是 T1.next 和 T2.next 指向的是 B 節(jié)點,如下圖所示:
死循環(huán)執(zhí)行步驟2
死循環(huán)的第二步操作是,線程 T2 時間片用完進(jìn)入休眠狀態(tài),而線程 T1 開始執(zhí)行擴(kuò)容操作,一直到線程 T1 擴(kuò)容完成后,線程 T2 才被喚醒,擴(kuò)容之后的場景如下圖所示:
從上圖可知線程 T1 執(zhí)行之后,因為是頭插法,所以 HashMap 的順序已經(jīng)發(fā)生了改變,但線程 T2 對于發(fā)生的一切是不可知的,所以它的指向元素依然沒變,如上圖展示的那樣,T2 指向的是 A 元素,T2.next 指向的節(jié)點是 B 元素。
死循環(huán)執(zhí)行步驟3
當(dāng)線程 T1 執(zhí)行完,而線程 T2 恢復(fù)執(zhí)行時,死循環(huán)就建立了,如下圖所示:
因為 T1 執(zhí)行完擴(kuò)容之后 B 節(jié)點的下一個節(jié)點是 A,而 T2 線程指向的首節(jié)點是 A,第二個節(jié)點是 B,這個順序剛好和 T1 擴(kuò)完容完之后的節(jié)點順序是相反的。T1 執(zhí)行完之后的順序是 B 到 A,而 T2 的順序是 A 到 B,這樣 A 節(jié)點和 B 節(jié)點就形成死循環(huán)了,這就是 HashMap 死循環(huán)導(dǎo)致的原因。
解決方案
HashMap 死循環(huán)的常用解決方案有以下幾個:
1.升級到高版本 JDK(JDK 1.8 以上),高版本 JDK 使用的是尾插法插入新元素的,所以不會產(chǎn)生死循環(huán)的問題;
2.使用線程安全容器 ConcurrentHashMap 替代(推薦使用此方案);
3.使用線程安全容器 Hashtable 替代(性能低,不建議使用);
4.使用 synchronized 或 Lock 加鎖 HashMap 之后,再進(jìn)行操作,相當(dāng)于多線程排隊執(zhí)行(比較麻煩,也不建議使用)。
小結(jié)
HashMap 死循環(huán)發(fā)生在 JDK 1.7 版本中,形成死循環(huán)的原因是 HashMap 在 JDK 1.7 使用的是頭插法,頭插法 + 多線程并發(fā)操作 + HashMap 擴(kuò)容,這幾個點加在一起就形成了 HashMap 的死循環(huán),解決死循環(huán)可以采用線程安全容器 ConcurrentHashMap 替代。
到此這篇關(guān)于深入探究Java中的HashMap為什么會產(chǎn)生死循環(huán)的文章就介紹到這了,更多相關(guān)Java HashMap死循環(huán)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot 用監(jiān)聽器統(tǒng)計在線人數(shù)案例分析
這篇文章主要介紹了springboot 用監(jiān)聽器統(tǒng)計在線人數(shù)案例分析,質(zhì)是統(tǒng)計session 的數(shù)量,思路很簡單,具體實例代碼大家參考下本文2018-02-02JAVA開發(fā)常用類庫UUID、Optional、ThreadLocal、TimerTask、Base64使用方法與實例詳
這篇文章主要介紹了JAVA開發(fā)常用類庫UUID、Optional、ThreadLocal、TimerTask、Base64使用方法與實例詳解,需要的朋友可以參考下2020-02-02Java中的數(shù)組基礎(chǔ)知識學(xué)習(xí)教程
這篇文章主要介紹了Java中的數(shù)組基礎(chǔ)知識學(xué)習(xí)教程,文中同時也整理了Java對數(shù)字類型的支持狀況及Number類中的方法,需要的朋友可以參考下2016-02-02java中mybatis和hibernate的用法總結(jié)
在本篇文章里小編給大家整理的是一篇關(guān)于java中mybatis和hibernate的用法總結(jié)內(nèi)容,有興趣的朋友們可以學(xué)習(xí)參考下。2021-01-01PowerJobAutoConfiguration自動配置源碼流程解析
這篇文章主要為大家介紹了PowerJobAutoConfiguration自動配置源碼流程解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12spring-boot整合Micrometer+Prometheus的詳細(xì)過程
這篇文章主要介紹了springboot整合Micrometer+Prometheus的詳細(xì)過程,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧2024-05-05