HashMap 在 Java7 ,Java8 的線程安全問題


1.Java7 多線程 put

put -> 容量到達上限 -> 擴容(resize) -> transfer (轉移舊散列表上的節點到新散列表)

在 transfer 這一步,因為Java7 使用了頭插法,可能會導致某個線程的新散列表的某個槽成環

本質問題是 假如一個線程已經 transfer 完畢,因為使用頭插法,會把鏈表逆置(圖中原本的 A -> B , 被置為 B -> A)

如此一來,另外一個線程transfer 的時候,會保存一個錯誤的 A -> B 關系,把 A 當成當前節點 e,把 B 當成下一個節點 next。

但是現在實際的指向關系是 B -> A , 如此一來,e 和 next 先后是

A  B

B  A

A  null

因為使用頭插法,在 B 還指向 A 的情況下,把 A 頭插到 B 前面,成環,下次訪問A或B會造成死循環,空耗CPU資源。

 

2.Java 8 不再使用上述頭插法,但是因為 沒有 StoreLoad 屏障,在一般的 TSO CPU模型中,StoreBuffer中的內容無法被及時刷出,可能出現覆蓋現象

關於TSO內存模型:https://www.cnblogs.com/lqlqlq/p/13693876.html 

假設有兩個CPU核心,在跑兩個線程,第一個CPU跑線程A,第二個CPU跑線程B

線程A 和 線程B 讀取 散列數組的 i 位置 元素為空,所以都打算直接寫入內容,線程A寫入 m ,線程B寫入 n 

因為有緩存一致性協議,所以可以把緩存和內存看成一個統一的一致的存儲系統

假設 線程 A 所在 CPU 先將 storeBuffer 的內容刷入 存儲系統

 

爾后,線程B 所在 CPU 也把 storeBuffer 的內容刷入存儲系統

顯然,線程A 的寫入會被線程 B 的覆蓋

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM