HashMap(二):高并發(fā)下的HashMap

原文出處: Hosee
參考:https://www.cnblogs.com/qingyunzong/p/9143249.html

HashMap的原理以及如何實現(xiàn)忱嘹,之前在JDK7與JDK8中HashMap的實現(xiàn)中已經說明了。

那么,為什么說HashMap是線程不安全的呢撤逢?它在多線程環(huán)境下,會發(fā)生什么情況呢?

1. resize死循環(huán)

我們都知道HashMap初始容量大小為16,一般來說,當有數(shù)據(jù)要插入時麻敌,都會檢查容量有沒有超過設定的thredhold,如果超過掂摔,需要增大Hash表的尺寸术羔,但是這樣一來,整個Hash表里的元素都需要被重算一遍乙漓。這叫rehash级历,這個成本相當?shù)拇蟆?/p>

void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }
 
        Entry[] newTable = new Entry[newCapacity];
        transfer(newTable, initHashSeedAsNeeded(newCapacity));
        table = newTable;
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
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;
            }
        }
}

大概看下transfer:

  1. 對索引數(shù)組中的元素遍歷
  2. 對鏈表上的每一個節(jié)點遍歷:用 next 取得要轉移那個元素的下一個,將 e 轉移到新 Hash 表的頭部簇秒,使用頭插法插入節(jié)點鱼喉。
  3. 循環(huán)2,直到鏈表節(jié)點全部轉移
  4. 循環(huán)1趋观,直到所有索引數(shù)組全部轉移

經過這幾步,我們會發(fā)現(xiàn)轉移的時候是逆序的锋边。假如轉移前鏈表順序是1->2->3皱坛,那么轉移后就會變成3->2->1。這時候就有點頭緒了豆巨,死鎖問題不就是因為1->2的同時2->1造成的嗎剩辟?所以,HashMap 的死鎖問題就出在這個transfer()函數(shù)上往扔。

1.1 單線程 rehash 詳細演示

單線程情況下贩猎,rehash 不會出現(xiàn)任何問題:

  • 假設hash算法就是最簡單的 key mod table.length(也就是數(shù)組的長度)。
  • 最上面的是old hash 表萍膛,其中的Hash表的 size = 2, 所以 key = 3, 7, 5吭服,在 mod 2以后碰撞發(fā)生在 table[1]
  • 接下來的三個步驟是 Hash表 resize 到4,并將所有的 <key,value> 重新rehash到新 Hash 表的過程

如圖所示:

1.2 多線程 rehash 詳細演示

為了思路更清晰蝗罗,我們只將關鍵代碼展示出來

while(null != e) {
    Entry<K,V> next = e.next;
    e.next = newTable[i];
    newTable[i] = e;
    e = next;
}
  1. Entry<K,V> next = e.next;——因為是單鏈表艇棕,如果要轉移頭指針,一定要保存下一個結點串塑,不然轉移后鏈表就丟了
  2. e.next = newTable[i];——e 要插入到鏈表的頭部沼琉,所以要先用 e.next 指向新的 Hash 表第一個元素(為什么不加到新鏈表最后?因為復雜度是 O(N))
  3. newTable[i] = e;——現(xiàn)在新 Hash 表的頭指針仍然指向 e 沒轉移前的第一個元素桩匪,所以需要將新 Hash 表的頭指針指向 e
  4. e = next——轉移 e 的下一個結點
    假設這里有兩個線程同時執(zhí)行了put()操作打瘪,并進入了transfer()環(huán)節(jié):
while(null != e) {
    Entry<K,V> next = e.next; //線程1執(zhí)行到這里被調度掛起了
    e.next = newTable[i];
    newTable[i] = e;
    e = next;
}

那么現(xiàn)在的狀態(tài)為:

從上面的圖我們可以看到,因為線程1的 e 指向了 key(3),而 next 指向了 key(7)闺骚,在線程2 rehash 后桃移,就指向了線程2 rehash 后的鏈表。

然后線程1被喚醒了:

  1. 執(zhí)行e.next = newTable[i]葛碧,于是 key(3)的 next 指向了線程1的新 Hash 表借杰,因為新 Hash 表為空,所以e.next = null进泼,
  2. 執(zhí)行newTable[i] = e蔗衡,所以線程1的新 Hash 表第一個元素指向了線程2新 Hash 表的 key(3)。好了乳绕,e 處理完畢绞惦。
  3. 執(zhí)行e = next,將 e 指向 next洋措,所以新的 e 是 key(7)

然后該執(zhí)行 key(3)的 next 節(jié)點 key(7)了:

  1. 現(xiàn)在的 e 節(jié)點是 key(7)济蝉,首先執(zhí)行Entry<K,V> next = e.next,那么 next 就是 key(3)了
  2. 執(zhí)行e.next = newTable[i],于是key(7) 的 next 就成了 key(3)
  3. 執(zhí)行newTable[i] = e菠发,那么線程1的新 Hash 表第一個元素變成了 key(7)
  4. 執(zhí)行e = next王滤,將 e 指向 next,所以新的 e 是 key(3)

這時候的狀態(tài)圖為:

然后又該執(zhí)行 key(7)的 next 節(jié)點 key(3)了:

  1. 現(xiàn)在的 e 節(jié)點是 key(3)滓鸠,首先執(zhí)行Entry<K,V> next = e.next,那么 next 就是 null
  2. 執(zhí)行e.next = newTable[i]雁乡,于是key(3) 的 next 就成了 key(7)
  3. 執(zhí)行newTable[i] = e,那么線程1的新 Hash 表第一個元素變成了 key(3)
  4. 執(zhí)行e = next糜俗,將 e 指向 next踱稍,所以新的 e 是 key(7)

這時候的狀態(tài)如圖所示:

很明顯,環(huán)形鏈表出現(xiàn)了S颇ā珠月!當然,現(xiàn)在還沒有事情楔敌,因為下一個節(jié)點是 null啤挎,所以transfer()就完成了,等put()的其余過程搞定后梁丘,HashMap 的底層實現(xiàn)就是線程1的新 Hash 表了侵浸。

2. fail-fast

如果在使用迭代器的過程中有其他線程修改了map,那么將拋出ConcurrentModificationException氛谜,這就是所謂fail-fast策略掏觉。

這個異常意在提醒開發(fā)者及早意識到線程安全問題,具體原因請查看ConcurrentModificationException的原因以及解決措施

順便再記錄一個HashMap的問題:

為什么String, Interger這樣的wrapper類適合作為鍵值漫? String, Interger這樣的wrapper類作為HashMap的鍵是再適合不過了澳腹,而且String最為常用。因為String是不可變的,也是final的酱塔,而且已經重寫了equals()和hashCode()方法了沥邻。其他的wrapper類也有這個特點。不可變性是必要的羊娃,因為為了要計算hashCode()唐全,就要防止鍵值改變,如果鍵值在放入時和獲取時返回不同的hashcode的話蕊玷,那么就不能從HashMap中找到你想要的對象邮利。不可變性還有其他的優(yōu)點如線程安全。如果你可以僅僅通過將某個field聲明成final就能保證hashCode是不變的垃帅,那么請這么做吧延届。因為獲取對象的時候要用到equals()和hashCode()方法,那么鍵對象正確的重寫這兩個方法是非常重要的贸诚。如果兩個不相等的對象返回不同的hashcode的話方庭,那么碰撞的幾率就會小些,這樣就能提高HashMap的性能酱固。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末械念,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子媒怯,更是在濱河造成了極大的恐慌订讼,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,817評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件扇苞,死亡現(xiàn)場離奇詭異,居然都是意外死亡寄纵,警方通過查閱死者的電腦和手機鳖敷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來程拭,“玉大人定踱,你說我怎么就攤上這事∈研” “怎么了崖媚?”我有些...
    開封第一講書人閱讀 157,354評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長恤浪。 經常有香客問我畅哑,道長,這世上最難降的妖魔是什么水由? 我笑而不...
    開封第一講書人閱讀 56,498評論 1 284
  • 正文 為了忘掉前任荠呐,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘泥张。我一直安慰自己呵恢,他們只是感情好,可當我...
    茶點故事閱讀 65,600評論 6 386
  • 文/花漫 我一把揭開白布媚创。 她就那樣靜靜地躺著渗钉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪钞钙。 梳的紋絲不亂的頭發(fā)上鳄橘,一...
    開封第一講書人閱讀 49,829評論 1 290
  • 那天,我揣著相機與錄音歇竟,去河邊找鬼挥唠。 笑死,一個胖子當著我的面吹牛焕议,可吹牛的內容都是我干的宝磨。 我是一名探鬼主播,決...
    沈念sama閱讀 38,979評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼盅安,長吁一口氣:“原來是場噩夢啊……” “哼唤锉!你這毒婦竟也來了?” 一聲冷哼從身側響起别瞭,我...
    開封第一講書人閱讀 37,722評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蝙寨,沒想到半個月后晒衩,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 44,189評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡墙歪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,519評論 2 327
  • 正文 我和宋清朗相戀三年听系,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片虹菲。...
    茶點故事閱讀 38,654評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡靠胜,死狀恐怖,靈堂內的尸體忽然破棺而出毕源,到底是詐尸還是另有隱情浪漠,我是刑警寧澤,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布霎褐,位于F島的核電站址愿,受9級特大地震影響,放射性物質發(fā)生泄漏瘩欺。R本人自食惡果不足惜必盖,卻給世界環(huán)境...
    茶點故事閱讀 39,940評論 3 313
  • 文/蒙蒙 一拌牲、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧歌粥,春花似錦塌忽、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至嬉探,卻和暖如春擦耀,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背涩堤。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評論 1 266
  • 我被黑心中介騙來泰國打工眷蜓, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人胎围。 一個月前我還...
    沈念sama閱讀 46,382評論 2 360
  • 正文 我出身青樓吁系,卻偏偏與公主長得像,于是被迫代替她去往敵國和親白魂。 傳聞我的和親對象是個殘疾皇子汽纤,可洞房花燭夜當晚...
    茶點故事閱讀 43,543評論 2 349

推薦閱讀更多精彩內容