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

原文地址:https://www.cnblogs.com/lqlqlq/p/13940088.html