Lazy loaded image
🐞ConcurrentHashMap Bug Jdk1.8
Words 880Read Time 3 min
2025-9-20
2025-9-20
type
status
date
slug
summary
tags
category
icon
password
原文

复现代码

♻️ 执行该代码会出现死循环
notion image
notion image
🎃
map.computeIfAbsent("AaAa",❶
❷ key -> map.computeIfAbsent("BBBB", key2 -> 42));
❶ ❷ 两处操作的 key 相同则会导致死循环。
 
page icon
“AaAa” ,”BBBB”的两个元素的 hash值相同,2031775
第一次执行computeIfAbsent方法,会先初始化 table 数组。
key 对应的桶还需要创建,每个桶都要一个头节点(链表法解决 hash冲突)
假设AaAa经过计算的索引在 在 0 号位置,cas替换节点后整个 hash数组结构如下。
[Node][Node]……[Node]
[key : null , value : null] 3️⃣
 
替换后会触发❷代码调用。因为前面已经执行过 table hash 数组初始化且对应的桶头节点已经初始化,此时computeIfAbsent方法的判断语句都可以略过,可以将元素添加桶中。关键的地方来了,在添加Node的代码时,判断Node的 hash 值(3️⃣处)是否≥0,但Node hash 值 为 -3,继续执行循环,无法break。
 
 

源码解释

1️⃣ 第一次执行computeIfAbsent时,初始化 table 数组
2️⃣ 桶节点初始化
Node<K,V> r = new ReservationNode<K,V>(); 桶节点元素
这一步骤会使用CAS 替换桶中元素。
3️⃣ 触发第二次computeIfAbsent方法执行
map.computeIfAbsent("BBBB", key2 -> 42)
4️⃣ 添加元素
第二次执行时,不会再执行1️⃣ 2️⃣ 3️⃣ 步骤,前面已经初始化 hash 数组和桶。
5️⃣ if (tabAt(tab, i) == f) // 这里是为 true
这里比较的引用,tabAt(tab, i)取 hash 数组中某个索引值,
f 是 2️⃣ 步骤的创建的对象
Node<K,V> r = new ReservationNode<K,V>();
6️⃣ if (fh >= 0)
fh 是2️⃣步骤创建的对象的 hash值,对象内容如下,此时Key、Value 都没有设置值。
Node → [key : null , value : null]
此时 Node 的 hash 为 -3,所以这个判断不成立,下面的代码不会执行,所以不会 break 循环。
7️⃣ if (binCount != 0)
binCount 计数器,用于记录当前操作的哈希桶中节点的数量。
2️⃣ 3️⃣ 步骤之间,binCount 赋值为 1,执行到这一步该条件也不成立,所以也这里也无法 break 循环。代码继续跳转到 1️⃣ 继续死循环。
 

原因

操作的两个 key hash 值相同,在同一个桶中进行操作。
第二个元素操作的时候,发现第一个桶已经有元素操作,且无法满足循环语句中能够 break 循环的 if 条件,导致产生了死循环。
 
 
上一篇
Xxl-Job-路由
下一篇
Seata TCC示例

Comments
Loading...