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