為何 HashMap 非線程安全

以 JDK8 的 HashMap 為例码撰。

HashMap 的 put 方法

1??情況一:
線程甲和線程乙共同對(duì) HashMap 進(jìn)行 put 操作。假設(shè)甲乙插入的 Key-Value 中 key 的 hashcode 是相同的嗜历,這說(shuō)明該鍵值對(duì)將會(huì)插入到 Table 的同一個(gè)下標(biāo)的位置抒线,會(huì)發(fā)生哈希碰撞胰挑,此時(shí) HashMap 按照平時(shí)的做法是形成一個(gè)鏈表(若超過(guò)八個(gè)節(jié)點(diǎn)則是紅黑樹)∮绶剩現(xiàn)在插入的下標(biāo)為 null(Table[i]==null) 則進(jìn)行正常的插入,此時(shí)線程甲進(jìn)行到了這一步正準(zhǔn)備插入训裆,被堵塞眶根,線程乙獲得運(yùn)行時(shí)間,進(jìn)行同樣操作边琉,也是 Table[i]==null属百,此時(shí)它直接運(yùn)行完整個(gè) put 方法,成功將元素插入变姨。隨后線程甲獲得運(yùn)行時(shí)間接著上面的判斷繼續(xù)運(yùn)行族扰,進(jìn)行了 Table[i]==null 的插入(此時(shí)實(shí)際是 Table[i]!=null 的操作,因?yàn)榍懊婢€程乙已經(jīng)插入元素了)定欧,這樣就會(huì)直接覆蓋線程乙插入的數(shù)據(jù)别伏,如此非線程安全。在 hashmap 做 put 操作的時(shí)候會(huì)調(diào)用到以下的方法:

void addEntry(int hash, K key, V value, int bucketIndex) {
    Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
        if (size++ >= threshold)
            resize(2 * table.length);
}

甲乙線程同時(shí)對(duì)同一個(gè)數(shù)組位置調(diào)用 addEntry忧额,兩個(gè)線程會(huì)同時(shí)得到現(xiàn)在的頭結(jié)點(diǎn),然后甲寫入新的頭結(jié)點(diǎn)之后愧口,乙也寫入新的頭結(jié)點(diǎn)睦番,那乙的寫入操作就會(huì)覆蓋甲的寫入操作造成甲的寫入操作丟失。

2??情況二:

void transfer(Entry[] newTable, boolean rehash) {  
    int newCapacity = newTable.length;  
    for (Entry<K,V> e : table) {  
        while(null != e) {  
            Entry<K,V> next = e.next;           
            if (rehash) {  
                e.hash = null == e.key ? 0 : hash(e.key);  
            }  
            int i = indexFor(e.hash, newCapacity);   
            e.next = newTable[i];  
            newTable[i] = e;  
            e = next;  
        } 
    }
}  

這種情況是 resize 的時(shí)候造成的。假設(shè) HashMap 中的 Table 情況如下:

甲乙線程要對(duì)同一個(gè) HashMap 進(jìn)行 put 操作托嚣。插入后 Table 變?yōu)椋?div id="txsvrq7" class="image-package">

此時(shí)線程甲乙都需要對(duì) HashMap 進(jìn)行擴(kuò)容巩检。假設(shè)線程甲沒(méi)有堵塞,順利完成 resize 后 Table 如下(這里的元素位置都是假設(shè)的):

如果線程乙的 resize 是在 Entry3 的時(shí)候堵塞的示启,那么當(dāng)它再次執(zhí)行的時(shí)候就會(huì)造成如下圖處形成一個(gè)循環(huán)鏈表兢哭,當(dāng)進(jìn)行 get 操作時(shí)候可能陷入死循環(huán)。

原因是:
線程乙獲得 CPU 時(shí)e = Entry3夫嗓,next = Entry2; 正常賦值迟螺,然后進(jìn)行下一次循環(huán)遍歷時(shí)要注意,此時(shí) HashMap 已經(jīng)是被線程甲 resize 過(guò)的了舍咖,那么就有 e = Entry2矩父,next = Entry3;頭插法插入此時(shí):

接著循環(huán),e = Entry3排霉,next = Entry3.next = null(看圖)窍株,此時(shí)再頭插就會(huì)形成循環(huán)鏈表了。

循環(huán)

頭插法代碼:
頭插法

當(dāng)多個(gè)線程同時(shí)檢測(cè)到總數(shù)量超過(guò)門限值的時(shí)候就會(huì)同時(shí)調(diào)用 resize 操作攻柠,各自生成新的數(shù)組并 rehash 后賦給該 map 底層的數(shù)組 table球订,結(jié)果最終只有最后一個(gè)線程生成的新數(shù)組被賦給 table 變量,其他線程的均會(huì)丟失瑰钮。而且當(dāng)某些線程已經(jīng)完成賦值而其他線程剛開始的時(shí)候冒滩,就會(huì)用已經(jīng)被賦值的 table 作為原始數(shù)組,這樣也會(huì)有問(wèn)題飞涂。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末旦部,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子较店,更是在濱河造成了極大的恐慌士八,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,084評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件梁呈,死亡現(xiàn)場(chǎng)離奇詭異婚度,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)官卡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門蝗茁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人寻咒,你說(shuō)我怎么就攤上這事哮翘。” “怎么了毛秘?”我有些...
    開封第一講書人閱讀 163,450評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵饭寺,是天一觀的道長(zhǎng)阻课。 經(jīng)常有香客問(wèn)我,道長(zhǎng)艰匙,這世上最難降的妖魔是什么限煞? 我笑而不...
    開封第一講書人閱讀 58,322評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮员凝,結(jié)果婚禮上署驻,老公的妹妹穿的比我還像新娘。我一直安慰自己健霹,他們只是感情好旺上,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,370評(píng)論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著骤公,像睡著了一般抚官。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上阶捆,一...
    開封第一講書人閱讀 51,274評(píng)論 1 300
  • 那天凌节,我揣著相機(jī)與錄音,去河邊找鬼洒试。 笑死倍奢,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的垒棋。 我是一名探鬼主播卒煞,決...
    沈念sama閱讀 40,126評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼叼架!你這毒婦竟也來(lái)了畔裕?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,980評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤乖订,失蹤者是張志新(化名)和其女友劉穎扮饶,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體乍构,經(jīng)...
    沈念sama閱讀 45,414評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡甜无,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,599評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了哥遮。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片岂丘。...
    茶點(diǎn)故事閱讀 39,773評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖眠饮,靈堂內(nèi)的尸體忽然破棺而出奥帘,到底是詐尸還是另有隱情,我是刑警寧澤仪召,帶...
    沈念sama閱讀 35,470評(píng)論 5 344
  • 正文 年R本政府宣布寨蹋,位于F島的核電站牲距,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏钥庇。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,080評(píng)論 3 327
  • 文/蒙蒙 一咖摹、第九天 我趴在偏房一處隱蔽的房頂上張望评姨。 院中可真熱鬧,春花似錦萤晴、人聲如沸吐句。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)嗦枢。三九已至,卻和暖如春屯断,著一層夾襖步出監(jiān)牢的瞬間文虏,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工殖演, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留氧秘,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,865評(píng)論 2 370
  • 正文 我出身青樓趴久,卻偏偏與公主長(zhǎng)得像丸相,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子彼棍,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,689評(píng)論 2 354