You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何ConcurrentHashMap的computeIfAbsent在JDK8无限循环,JDK17抛异常?

ConcurrentHashMap在JDK8与JDK17中的异常行为解析

问题代码示例

示例1:bad() 方法

public void bad() {
    final ConcurrentMap<String, Integer> chm = new ConcurrentHashMap<>();
    final String key = "1";
    chm.computeIfAbsent(key, __ -> {
        chm.remove(key);
        return 1;
    });
}

示例2:bad2() 方法

public void bad2() {
    final ConcurrentMap<String, Integer> chm = new ConcurrentHashMap<>();
    final String key = "1";
    Thread worker = new Thread(() -> chm.remove("1"));
    chm.computeIfAbsent(key, __ -> {
        worker.start();
        try {
            worker.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return 1;
    });
}

JDK8中无限循环的根源

ConcurrentHashMap的computeIfAbsent执行时,会先锁定目标key对应的哈希桶。在lambda里调用chm.remove(key)时,当前线程已经持有该桶的锁,但JDK8的实现没有锁重入处理逻辑,反而会进入自旋等待锁释放:

  • 对bad()方法:lambda里的remove操作等待computeIfAbsent释放锁,而computeIfAbsent要等lambda执行完才释放锁,形成自死锁式的无限循环。
  • 对bad2()方法:worker线程调用remove时,需要获取同一哈希桶的锁,但主线程在computeIfAbsent里拿着锁,还在等worker线程结束;worker线程又卡在等主线程释放锁,双向等待导致线程间死锁,进而无限循环。

JDK17中抛出异常的原因

JDK17给ConcurrentHashMap的computeIfAbsent加了递归更新检测机制。当发现当前线程持有锁的情况下,又对同一key执行修改操作(比如这里的remove),会直接抛出IllegalStateException: Recursive update——目的就是提前阻断JDK8里那种无限循环的死锁场景,让开发者能立刻定位到代码里的逻辑错误。

内容的提问来源于stack exchange,提问作者ECPerth

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 00:10:13