1. 程式人生 > >併發下HashMap為什麼不是執行緒安全的?

併發下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;

時,至此,迴圈連結串列形成了。


另外大家也可以看小灰漫畫中的一篇文章,寫的也很清楚: https://mp.weixin.qq.com/s?__biz=MzI2NjA3NTc4Ng==&mid=2652079766&idx=1&sn=879783e0b0ebf11bf1a5767933d4e61f&chksm=f1748d73c6030465fe6b9b3fa7fc816d4704c91bfe46cb287aefccee459153d3287172d91d23&scene=21#wechat_redirect