之前我已經寫過關於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...