HashMap 執行緒不安全的原因

2022-07-11 00:57:13 字數 2123 閱讀 2909

hashmap執行緒安全的問題,在各大面試中都會被問到,屬於常考熱點題目。雖然大部分讀者都了解它不是執行緒安全的,但是再深入一些,問它為什麼不是執行緒安全的,仔細說說原理,用圖畫出一種非執行緒安全的情況?1.8之後又做了什麼改善了這點?很多讀者可能一時想不出很好的答案。

我們關注下面的**

void transfer(entry newtable)

while (e != null);}}

}

這段**的主要做的是rehash,其中的核心**是12~16行,下面畫圖分析,當有多個執行緒同時做rehash時,會發生什麼。

假設條件如下:

擴容前的圖示如下:

執行緒2執行完成rehash和執行緒1執行到12掛起的圖示如下:

然後我們一步一步的分析執行緒1繼續rehash的情況

當e為key3時,經過12~16步後的圖示如下,我們發現3被插入到了頭部,並且形成了環形鍊錶

因為e不為null,所以我們繼續執行12~16行,執行完畢後如圖所示

至此兩個執行緒的擴容都完畢,形成了環形鍊錶。

所以當呼叫get方法時,因為環形鍊錶的存在,形成乙個死迴圈,佔滿cpu。

首先說明,1.8版本的resize方法做了一些優化,優化的點主要在於,當hashmap的size擴容為2倍時,其實不需要每個元素都計算hash值,元素在新陣列中的位置只有以下兩種情況:

為什麼只有這兩種情況呢?我們看下圖:

a是原陣列長度-1,b是擴容為2倍的新陣列長度-1,對於第一行n-1來說,其實就多了從右往左的第五位的1。

對於key1和key2來說,key1的第五位是0,key2的第五位是1,所以rehash後:

明白這點後,我們繼續關注下面的**,因為resize方法中還有一些和問題領域不那麼相關的**,所以我只貼上出分析問題的必需**。

node newtab = (node)new node[newcap];

table = newtab;

if (oldtab != null)

//代表了多出來的第x位為1的情況

else

} while ((e = next) != null);

//走到這裡,就已經把原鍊錶結點分成了兩組

//設定位置不變組的陣列頭結點為lohead結點

if (lotail != null)

//設定新陣列的頭結點為lohead結點

//設定位置變為原位置 + 原陣列長度 組的陣列頭結點為hihead結點

if (hitail != null) }}

}}

相關資料

我們依舊按照上面的假設畫圖分析

假設條件如下:

因為1.8版本的插入過程修改為了尾插法,插入前的圖示變為如下所示

執行緒2執行完成rehash和執行緒1執行到19行掛起時狀態如下:

執行緒1繼續rehash,走完7和3之後的結果如下

這裡我們可以看到,並沒有形成環形鍊錶,所以使用尾插法解決了1.7版本在文中分析情況下的環形鍊錶問題。

通過上文的分析,我們解決了前言中提出的兩個問題。

hashMap的執行緒不安全

hashmap是非執行緒安全的,表現在兩種情況下 1 擴容 t1執行緒對map進行擴容,此時t2執行緒來讀取資料,原本要讀取位置為2的元素,擴容後此元素位置未必是2,則出現讀取錯誤資料。2 hash碰撞 兩個執行緒新增元素發生hash碰撞,都要將此元素新增到鍊錶的頭部,則會發生資料被覆蓋。詳情 ha...

談談HashMap執行緒不安全的體現

hashmap的原理以及如何實現,之前在 jdk7與jdk8中hashmap的實現 中已經說明了。那麼,為什麼說hashmap是執行緒不安全的呢?它在多執行緒環境下,會發生什麼情況呢?1.resize死迴圈 我們都知道hashmap初始容量大小為16,一般來說,當有資料要插入時,都會檢查容量有沒有超...

hashmap的執行緒不安全性

首先hashmap在多個執行緒同時對其操作的時候造成的髒讀很統一理解,比如乙個執行緒a對hashmap進行讀操作,乙個執行緒b對hashmap就行寫操作。執行緒b先進入put方法中,此時還沒有寫資料的時候執行緒a輪轉執行,並一直執行到結束,假設執行取到資料為條,這時執行緒b繼續執行新增了一條資料。那...