HashMap中的resize以及死鏈的情況

2021-09-07 14:58:23 字數 3439 閱讀 8297

之前我已經寫過關於hashmap的內容了:

我們都知道hashmap是執行緒不安全的, 如果多執行緒來訪問會有什麼問題呢? 答案是會造成死鎖。

接下來我們就分析下為何會造成死鎖。

說到hashmap中死鎖的情況, 我們就必須要先講下resize()方法, 顧名思義, 這個方法就是來擴容的。

當hashmap的size超過

thredshold時, 就需要擴容了。 當我們put時:

(截圖**為jdk7 hashmap原始碼)

首先,我們需要知道幾個最基本的概念: entry

table的初始化長度length(預設值是16),load factor為負載因子(預設值是0.75),threshold是hashmap所能容納的最大資料量的entry(鍵值對)個數。size是hashmap中實際存在 的鍵值對數量。threshold = length * load factor。也就是說,在陣列定義好長度之後,負載因子越大,所能容納的鍵值對個數越多。

接著我們直接看上面的**, 當size >= threshold且table[bucketindex]不為空就會觸發resize操作。 然後看resize()方法:

這裡重點就是transfer方法, 接著我們來看transfer方法:

第一: 遍歷舊的table

第二: 將舊的table中每個元素重新計算hash值, 然後賦予新的table中

具體我們將用圖示的方法來解析:

單執行緒擴容:

假設:hash演算法就是簡單的key與length(陣列長度)求餘。

hash表長度為2,如果不擴容, 那麼元素key為3,5,7按照計算(key%table.length)的話都應該碰撞到table[1]上

擴容:hash表長度會擴容為4

重新hash,key=3 會落到table[3]上(3%4=3), 當前e.next為key(7), 繼續while迴圈

重新hash,key=7 會落到table[3]上(7%4=3), 產生碰撞, 這裡採用的是頭插入法,所以key=7的entry會排在key=3前面(這裡可以具體看while語句中**)

當前e.next為key(5), 繼續while迴圈

重新hash,key=5 會落到table[1]上(5%4=3), 當前e.next為null, 跳出while迴圈, resize結束

如題如圖所示:

多執行緒擴容:

這裡我們先把核心**搬出來, 方便檢視

while(null != e)

去掉了一些冗餘的**, 層次結構更加清晰了。

第一行:記錄odl hash表中e.next

第二行:rehash計算出陣列的位置(hash表中桶的位置)

第三行:e要插入鍊錶的頭部, 所以要先將e.next指向new hash表中的第乙個元素

第四行:將e放入到new hash表的頭部

核心**如上所說, 下面就是多執行緒同時put的情況了, 然後同時進入transfer方法中:

假設這裡有兩個執行緒同時執行了

put()操作,並進入了

transfer()

環節

那麼現在的狀態為:

然後執行緒1被喚醒了:

執行e.next = newtable[i],於是 key(3)的 next 指向了執行緒1的新 hash 表,因為新 hash 表為空,所以

e.next = null,執行

newtable[i] = e

,所以執行緒1的新 hash 表第乙個元素指向了執行緒2新 hash 表的 key(3)。好了,e 處理完畢。

執行e = next

,將 e 指向 next,所以新的 e 是 key(7)

然後該執行 key(3)的 next 節點 key(7)了:

現在的 e 節點是 key(7),首先執行

entrynext = e.next

,那麼 next 就是 key(3)了

執行e.next = newtable[i]

,於是key(7) 的 next 就成了 key(3)

執行newtable[i] = e

,那麼執行緒1的新 hash 表第乙個元素變成了 key(7)

執行e = next

,將 e 指向 next,所以新的 e 是 key(3)

這時候的狀態圖為:

然後又該執行 key(7)的 next 節點 key(3)了:

現在的 e 節點是 key(3),首先執行

entrynext = e.next

,那麼 next 就是 null

執行e.next = newtable[i]

,於是key(3) 的 next 就成了 key(7)

執行newtable[i] = e

,那麼執行緒1的新 hash 表第乙個元素變成了 key(3)

執行e = next

,將 e 指向 next,所以新的 e 是 key(7)

這時候的狀態如圖所示:

很明顯,環形鍊錶出現了!!當然,現在還沒有事情,因為下乙個節點是 null,所以

transfer()就完成了,等

put()

的其餘過程搞定後,hashmap 的底層實現就是執行緒1的新 hash 表了。

參考:

HashMap中的resize問題

在jdk1.8中,hashmap的resize 函式做了相應的調整,尤其是對於在buckets的鍊錶中,官方給出的該resize 函式主要在兩種情況下使用 初始化的時候 將雜湊表擴容成之前的兩倍時 下面首先看初始化時,實際的resize 函式做了哪些工作 final node resize node...

HashMap的擴容機制 resize

hashmap底層邏輯 當我們往hashmap中put元素的時候,先根據key的hash值得到這個元素在陣列中的位置 即下標 然後就可以把這個元素放到對應的位置中了。如果這個元素所在的位子上已經存放有其他元素了,那麼在同乙個位子上的元素將以鍊錶的形式存放,新加入的放在鏈頭,最先加入的放在鏈尾。從ha...

HashMap中的resize以及死鏈的情況

說到hashmap中死鎖的情況,我們就必須要先講下resize 方法,顧名思義,這個方法就是來擴容的。當hashmap的size超過 thredshold時,就需要擴容了。當我們put時 截圖 為jdk hashmap原始碼 首先,我們需要知道幾個最基本的概念 entry table的初始化長度le...