并发下HashMap为什么不是线程安全的?
首先看下HashMap的工作原理,我們回顧一下HashMap的結構:
HashMap的結構就是哈希表,底層是一個數組,這個數組中盡可能地分散所有的key,通過key的hash值得到數組下標,然后把entry插到該數組,假如有兩個不同的key被分到相同的下標,也就是哈希沖突,那么該數組在該下標下就會形成鏈表。
為了減少沖突,我們需要時刻留意當前的size是否太大,檢查是否需要擴容,一旦超過設定的threshold,那么就要重新增大數組尺寸,此時所有元素都需要重新計算應該放置的下標。
擴容、rehash
上面為擴容的方法,可以看到,實際上擴容就是新建了一個數組,同時調用了transfer方法,給新數組賦值后覆蓋掉原來的table。那我們來看看transfer里面發生了什么:
從代碼上可以看出,原來鏈表的元素有可能已經不在原來的數組上,也就是元素都被重新排到數組上了。
圖解rehash
擴容前的結構:
在transfer方法中,對原來的table進行遍歷重排序,我們來模擬一下這個過程。重新排序的關鍵在于,數組長度變了以后,重新計算得出的下標,這與哈希算法有關,為了便于理解,我們簡單地認為本HashMap的哈希算法為用key 去mod數組的長度,這也是理解transfer如何重新排列鏈表的關鍵。
這是第一次循環后的結果:
第二步:
繼續遍歷,最終結果如下
以上就是在單線程下,重新排列后的鏈表。
多線程下的rehash
我們從上文看到,單線程下的rehash是完全沒有問題的,那么在多線程下會出現什么問題呢?我們仍然用清晰明了的圖來做演示
首先,我們假設有兩個進程同時進行put操作,且讓HashMap進行擴容,同時進入transfer方法
此時的狀態如下:
然后線程2完成了transfer方法,如下
此時,又回到線程1,但此時,完成第一次循環過程:
繼續進行下一次循環:
然后e = next = key(3),再繼續下一次循環:
當執行完:
e.next = newTable[3];
newTable[3] = e;
時,至此,循環鏈表形成了。
總結
以上是生活随笔為你收集整理的并发下HashMap为什么不是线程安全的?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: win10安装TensorFlow填坑笔
- 下一篇: 【Java】最全面的英文单词单数复数形式