自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

JDK bug?? HashMap中的死循環(huán)問題!

開發(fā) 前端
HashMap 是 java工程師最常用的數(shù)據(jù)結(jié)構(gòu)之一,但是能對其原理掌握的比較深的同學(xué)很少。尤其是本文的主題,據(jù)一些常年負(fù)責(zé)招 聘的朋友介紹。

[[358225]]

本文轉(zhuǎn)載自微信公眾號「石杉的架構(gòu)筆記」,作者雨后晴天 。轉(zhuǎn)載本文請聯(lián)系石杉的架構(gòu)筆記公眾號。  

1、前言

HashMap 是 java工程師最常用的數(shù)據(jù)結(jié)構(gòu)之一,但是能對其原理掌握的比較深的同學(xué)很少。尤其是本文的主題,據(jù)一些常年負(fù)責(zé)招 聘的朋友介紹。

HashMap 的死循環(huán)是面試中的常見問題,但是能講清楚的面試者很少,即使這些應(yīng)聘者工作時間都比較長。

原因是,目前講解這個問題的文章雖多,但好文章卻不多。

也有些文章講解的很完善,但內(nèi)容太過燒腦,所以看下來基本上都云里霧 里。鑒于目前的情況,本文基于我個人對源碼熟練掌握的基礎(chǔ)上,跳出源碼,因?yàn)槟翘珶X,提煉出核心環(huán)節(jié)。盡量一步一圖,以大白話 形式將底層原理呈現(xiàn)出來。本文的理論基礎(chǔ)是基于 jdk1.8 以下版本。死循環(huán)問題也主要存在 于 jdk1.8 以下的版本中。

2、HashMap 的數(shù)據(jù)結(jié)構(gòu)

jdk1.7 版本的HashMap,底層結(jié)構(gòu)是一個數(shù)組,數(shù)組中的每項(xiàng)都可以是個鏈表,開始時我們用數(shù)組保存元素,當(dāng)添加元素遇到?jīng)_突,即相同位置處有多個元素時,就將這些元素添加到對應(yīng)的鏈表 中,所以 HashMap 的底層結(jié)構(gòu)可以認(rèn)為是由數(shù)組和鏈表構(gòu)成。

如果,你在構(gòu)建一個 HashMap 時不指定數(shù)組大小,那么默認(rèn)情況下,數(shù)組大小為 16,但限于篇幅。

下面我們只畫了一個大小為 8 的 數(shù)組數(shù)組,在索引為 2 處有個鏈表,存放著 3 個元素,分別為 Entry1、Entry2、Entry3。這三個元素的 hash 運(yùn)算結(jié)果是一樣的而 且都為 2,所以在數(shù)組的 index=2 構(gòu)成了一條鏈表。

 

3、插入數(shù)據(jù)

在往 HashMap 中添加元素時,比如需要在上面的HashMap 中 添加一個節(jié)點(diǎn) Entry4(k4,v4)。

首先會根據(jù)它的 key 值 k4 計算 hashcode 值,然后再對這個 hashcode 值做些復(fù)雜運(yùn)算,得到在數(shù)組中的目標(biāo) index,比如為 4, 這時因?yàn)閿?shù)組索引為 4 的位置為空,可直接將該元素插入到其底層數(shù) 組中。

 

因?yàn)椴煌?key 完全可能有相同的 hashcode 值,當(dāng)然也完全可能在數(shù)組中有相同的目標(biāo) index。比如,要新添加一個元素 Entry5(k5,v5),對 k5 進(jìn)行運(yùn)算得到的目標(biāo) index 為4。

但是因?yàn)閿?shù)組中對應(yīng)位置已有一個元素 Entry4, 我們清楚一個數(shù)組的同一個索引處不可能連續(xù)存儲兩個元素而不被覆 蓋,所以這時會怎么處理呢?

這時鏈表就派上用場了,將這兩個元素在 index 為 4 處構(gòu)成一個 鏈表即可以完美的解決沖突問題。

但是要注意,jdk1.7 采用的是頭插 法。就是將新節(jié)點(diǎn) Entry5 放在數(shù)組中作為鏈表的頭節(jié)點(diǎn),將原來的 Entry4 移出作為其 next 節(jié)點(diǎn)。如下圖所示:

 

為何要一直強(qiáng)調(diào)是 jdk1.7 版本,因?yàn)樵?jdk1.8 的時候 HashMap 結(jié)構(gòu)發(fā)生了較大的變化,1.8 版本的 HashMap 采用的是尾插法而且底 層結(jié)構(gòu)也發(fā)生了變化。

4、擴(kuò)容

java 工程師都清楚 jdk1.7 版本的 HashMap初始默認(rèn)長度為 16,你也可以自己定義,但不管你是自定義還是選擇默認(rèn)長度,

隨著 元素的增加并達(dá)到了一定的閾值,總是要擴(kuò)容的。擴(kuò)容時是按 2 倍來進(jìn)行的,就是創(chuàng)建一個 2 倍大小的數(shù)組,將原 來的元素重新 Hash,計算新的 index 放入到新的數(shù)組+鏈表結(jié)構(gòu)中。

 

5、高并發(fā)下的擴(kuò)容問題

HashMap 的這一結(jié)構(gòu)和擴(kuò)容機(jī)制可以保證,在大數(shù)據(jù)量情況 下,讀寫性能依然能保持優(yōu)良。

插入時采用頭插法會非常快速,讀取 時,也不會因?yàn)殒湵磉^長而影響讀取性能??吹竭@,會不會覺得 Hash 是個完美的設(shè)計,其實(shí)不是,因?yàn)?HashMap 并不是線程安全的數(shù)據(jù)結(jié)構(gòu)。

尤其是在擴(kuò)容時,如果并發(fā)量不大通常不會有什么問題,但在高 并發(fā)情況下,擴(kuò)容可能導(dǎo)致很嚴(yán)重的問題。我們下面來模擬一個高并發(fā)情況下擴(kuò)容的例子。

1)假設(shè)有一個 HashMap,它的負(fù)載因子為 1,有兩個元素(7,x)和 (11,y),它們 hash 運(yùn)算的結(jié)果都為 1,所以都在 1 號鏈表(即 index 為 1 所指向的鏈表中,為便于描述,下面都簡稱)中,如下所 示。

 

2)這時你需要往里面添加一個節(jié)點(diǎn)(15,z),這時有 3 個節(jié)點(diǎn),因?yàn)?已達(dá)到擴(kuò)容閾值,需要擴(kuò)容。

 

3)首先是線程 2 過來,按照擴(kuò)容的底層源碼,需要將 e 指針指向鏈表 的頭節(jié)點(diǎn)(7,x),next 指針指向下一個節(jié)點(diǎn)(11,y),如下圖所 示:

 

其中 e 表示當(dāng)前線程正要遷移的節(jié)點(diǎn),next 表示下一個需要遷移 的節(jié)點(diǎn)。如果 e 指向的節(jié)點(diǎn)遷移完成,則進(jìn)入下一次循環(huán),e 指針重新指 向節(jié)點(diǎn)(11,y),next 指針重新指向節(jié)點(diǎn)(15,z)。

4)但是當(dāng)線程 2 剛開始標(biāo)記好 e 和 next 兩個指針,正準(zhǔn)備遷移第一 個節(jié)點(diǎn)時。線程 1 過來了,并完成了遷移。

5)前面我們說過,HashMap 進(jìn)入鏈表是采用頭插法,所以對三個元 素 rehash 遷移后的鏈表順序?yàn)?15,z) —> (11,y) —> (7,x) 圖中的 e 和 next 指針屬于線程

2,它們還停留在原來的節(jié)點(diǎn)上, 這一階段的結(jié)構(gòu)如下圖所示:

 

這時線程 2 中的 table 有(7,x)這個節(jié)點(diǎn)。

6)然后將 next 指向的節(jié)點(diǎn)(11, y)添加到線程 2 表示的 HashMap 中,因?yàn)椴捎妙^插法,所以(11, y)成了鏈表的頭節(jié)點(diǎn),原來的(7, x)則成了它的下一個節(jié)點(diǎn)

 

這時線程 2 中的 table 有(11,y)---->(7,x)這些節(jié)點(diǎn),鏈 表的頭節(jié)點(diǎn)為(11,y)。

7)繼續(xù)循環(huán)遷移元素,將 e 指向(7,x),則 next 為 null。然后將 線程 2 的數(shù)組下標(biāo)索引 3 指向 e 指向的節(jié)點(diǎn),即將(7,x)又添加到 頭節(jié)點(diǎn)(頭插法),這時線程 2 中的 table 有(7,x)—>(11,y) ---->(7,x),構(gòu)成了環(huán),如圖所示:

 

6、問題很嚴(yán)重

如果上述的遷移過程最后以線程 2 的 table 作為新的 hashmap, 則最后的遷移結(jié)果如下圖所示:

 

7、環(huán)形鏈表會帶來什么后果呢?

假設(shè)我現(xiàn)在要查詢上述 HashMap 是否包含節(jié)點(diǎn)(7,x),首先根 據(jù) hash 運(yùn)算得到目標(biāo) index 為 3,所以查找目標(biāo)轉(zhuǎn)到了 3 號鏈表,

然后根據(jù) key 值為 11 判斷與鏈表的頭節(jié)點(diǎn)(7,x)的 key 恰好相等,再 判斷它們的 value 也相等。說明該 HashMap 中包含了我們所要查詢 的幾點(diǎn),返回 true。

假設(shè)我現(xiàn)在要查詢節(jié)點(diǎn)(11,y),經(jīng)過 hash 運(yùn)算也將查找目標(biāo)轉(zhuǎn) 到了 3 號鏈表,然后根據(jù) key 值為 11 判斷與頭節(jié)點(diǎn)(7,x)的 key 不 相等,則轉(zhuǎn)向下一個節(jié)點(diǎn)。

通過對比,發(fā)現(xiàn)與下一個節(jié)點(diǎn)的 key 和 value 都相等,則直接返回 true。

這樣看來似乎沒什么問題,然后我們再查一個節(jié)點(diǎn)(15,m),經(jīng) 過 hash 運(yùn)算也將查找目標(biāo)轉(zhuǎn)到了 3 號鏈表,首先與頭節(jié)點(diǎn)(7,x)判斷 不相等,然后與下一個節(jié)點(diǎn)(11,y)對比也不相等。再與下一個節(jié)點(diǎn) 判斷,這時鏈表中節(jié)點(diǎn)為(7,x)其實(shí)就是重新回到了頭節(jié)點(diǎn),

 

它的下一 個節(jié)點(diǎn)又是(11,y),這種搜索會一直無限循環(huán)下去,CPU 很快飆 升到 100%,后果很嚴(yán)重。其實(shí)不管你搜索什么節(jié)點(diǎn),只要路由到 3 號鏈表,并且待搜索的 key 不是 7 或 11,都將發(fā)生死循環(huán)。

 

 

責(zé)任編輯:武曉燕 來源: 石杉的架構(gòu)筆記
相關(guān)推薦

2013-06-06 13:34:56

HashMap線程不安全

2022-01-20 08:44:25

HashMap死循環(huán)開放性

2025-01-21 00:00:00

HashMap死循環(huán)數(shù)據(jù)損壞

2023-01-31 08:24:55

HashMap死循環(huán)

2022-01-18 06:59:50

HashMap循環(huán)底層

2020-09-29 15:24:07

面試數(shù)據(jù)結(jié)構(gòu)Hashmap

2020-05-27 12:45:52

HashMapJava加載因子

2018-10-10 20:20:14

2022-01-24 07:01:20

安全多線程版本

2011-08-29 16:23:29

Lua腳本

2011-09-07 10:13:04

IPv6IPv4

2024-12-06 16:00:00

C++頭文件

2024-10-09 09:07:10

JVM優(yōu)化String類JDK1.6

2013-06-06 13:10:44

HashMap無鎖

2022-05-10 12:20:04

JDKversion故障

2020-09-18 06:39:18

hashMap循環(huán)數(shù)據(jù)

2011-08-05 16:20:32

Java 7HotspotBug

2021-08-20 08:22:12

Tomcat原生線程池

2010-04-26 13:30:21

服務(wù)器虛擬化

2021-07-15 08:58:16

Spring對象引用
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號