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

ConcurrentHashMap异常行为:高并发下get偶现null问题排查

问题分析与解决方案

这种第一次get返回null、第二次读取正常的现象,本质是ConcurrentHashMap的无锁读与有锁写/结构调整之间的并发窗口导致的短暂不一致,具体原因分两种情况:

核心原因

1. 红黑树结构调整的中间状态

当ConcurrentHashMap中某个桶的链表长度超过阈值(默认8)时,会转为红黑树以提升查询效率。在更新已存在的键时,如果该键位于红黑树中,红黑树的结构调整(如左旋、右旋、节点替换)是多步非原子操作——虽然这些操作在桶级别的锁保护下执行,但get操作是无锁的,会直接遍历当前的树结构。如果get恰好落在结构调整的中间阶段,遍历到不一致的树结构就会找不到目标节点,返回null;当结构调整完成后,第二次get就能正常读取到值。

2. 渐进式扩容的迁移窗口

ConcurrentHashMap采用渐进式扩容,会分批将旧桶的节点迁移到新桶。在某个桶的迁移过程中,若get操作刚好访问到处于迁移中间状态的桶,也可能出现短暂的读取不到值的情况,但这种概率比红黑树调整的情况低很多。

需要明确的是:你没有执行删除操作的前提下,键对应的条目始终存在,只是并发读取恰好落在了写操作的“未完成窗口”中。

解决方案

1. 保留现有重试逻辑(推荐)

这种并发窗口的持续时间极短,你当前写的“第一次get返回null则重试一次”的逻辑,已经能覆盖绝大多数场景,且对性能影响极小,是最简单有效的处理方式。

2. 优化更新操作(可选)

如果希望进一步降低这种情况的发生概率,可以针对更新操作使用更细粒度的原子语义:

  • 若更新时不需要获取旧值,继续使用put即可,因为它本身就是线程安全的;
  • 若更新依赖旧值,可以使用merge或compute方法,这些方法会在锁保护下完成整个更新逻辑,减少结构调整的触发频率:
    // 示例:使用merge更新值,若key不存在则初始化(根据业务调整)
    map.merge(key, value, (oldVal, newVal) -> newVal);
    

3. 调整红黑树阈值(不推荐)

通过反射修改红黑树的转换阈值(如提高到更高的数值),减少红黑树的生成概率,但这种方式会牺牲哈希冲突后的查询性能,且属于依赖实现细节的hack,不建议在生产环境使用。

额外排查点

  • 确认Long类型的key没有被意外修改(虽然Long是不可变类型,但要避免使用自定义的包装类或存在自动拆装箱导致的异常);
  • 检查是否有其他线程在执行隐式的删除操作(如remove或clear),虽然你排除了这种可能,但高并发场景下需再次确认。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 21:44:59