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 的覆蓋