news 2026/8/14 21:38:12

Java ConcurrentHashMap 实战:computeIfAbsent 重入死锁、原子累加与 size 为什么不准

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java ConcurrentHashMap 实战:computeIfAbsent 重入死锁、原子累加与 size 为什么不准

Java ConcurrentHashMap 实战:computeIfAbsent 重入死锁、原子累加与 size 为什么不准

很多人把ConcurrentHashMap当成「加了锁的 HashMap」,以为随便用都线程安全。实际上它只保证单个方法调用的原子性,一旦你把「读—改—写」拆成几步,或者在computeIfAbsent里再动这个 map,坑就来了。这篇讲三个真实踩过的问题。

陷阱一:先 get 再 put 不是原子操作

统计词频最容易写出这种代码:

// 错误:get 和 put 之间可能被其他线程插队,计数丢失ConcurrentHashMap<String,Integer>map=newConcurrentHashMap<>();Integerc=map.get(word);map.put(word,c==null?1:c+1);

getput各自线程安全,但组合起来有竞态:两个线程同时读到 5,各自 +1 写回 6,少算一次。ConcurrentHashMap帮不了你,因为它根本不知道这两次调用要绑在一起。

正确做法是用一次原子调用完成累加。计数场景首选merge:

// 正确:merge 内部对该 key 加锁,读改写是原子的map.merge(word,1,Integer::sum);

或者用getOrDefault是只读的、compute系列是原子的,别把它们和裸put混着用。

陷阱二:computeIfAbsent 里再碰同一个 map = 死锁/异常

computeIfAbsent会对当前 key 所在的桶加锁,然后执行你的 lambda。如果 lambda 里又去写同一个 map,轻则抛异常,重则活锁。经典翻车:递归建缓存。

// 危险:mappingFunction 里又调了 computeIfAbsentmap.computeIfAbsent("a",k->{map.computeIfAbsent("b",k2->compute(k2));// 可能死锁/IllegalStateExceptionreturncompute(k);});

Java 9+ 对这种自引用会抛IllegalStateException: Recursive update,更早的版本可能直接卡死。修法是把「计算」和「放入」拆开,计算过程不碰 map:

// 先算好,再原子放入Valuev=map.get(key);if(v==null){Valuecomputed=expensiveCompute(key);// 纯计算,不碰 mapv=map.putIfAbsent(key,computed);// 已存在就用别人放的if(v==null)v=computed;}

putIfAbsent返回旧值:返回 null 说明是你放进去的,非 null 说明别人先放了,用它的即可,天然去重。

陷阱三:size() 是估算值,别拿去做精确判断

高并发下size()/isEmpty()返回的是某一瞬间的近似值,不保证和「此刻真实元素数」一致。下面这种「满了就停」的逻辑会出错:

// 不可靠:size() 在并发写入时可能读到旧值if(map.size()>=MAX)return;// 可能已经超了,也可能还没到map.put(key,val);

如果你需要「容量上限」这种强一致语义,ConcurrentHashMap本身不提供,应该用带计数的原子变量或专门的限流结构。size()只适合做监控、日志这类容忍误差的场景。

顺带一提:原子累加用 LongAdder 更快

如果只是全局计数(不是按 key 分组),别用ConcurrentHashMap存一个 key,直接上LongAdder,高并发下比AtomicLong少了 CAS 自旋冲突:

LongAddercounter=newLongAdder();counter.increment();// 内部分段累加,竞争小longtotal=counter.sum();

小结

  • ConcurrentHashMap只保证单次方法调用原子,get之后再put会丢更新;计数用merge(key, 1, Integer::sum)
  • computeIfAbsent的 lambda 里绝不能再写同一个 map,Java 9+ 会抛Recursive update;拆成「先算后 putIfAbsent」。
  • size()/isEmpty()估算值,不能用来做精确的容量判断。
  • 记忆点:读改写要合并成一次原子调用,计算逻辑别塞进 compute 的 lambda 里。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/14 21:38:05

从零制作百度输入法皮肤:双色主题设计与跨平台适配全攻略

之前想给手机输入法换个可爱的皮肤&#xff0c;找了一圈发现要么收费&#xff0c;要么风格不喜欢。自己动手做了一个“草莓熊”主题的百度输入法皮肤&#xff0c;没想到在 iOS 和安卓上都能用&#xff0c;双色切换效果还挺有趣。本文将手把手教你如何从零制作一款属于自己的百度…

作者头像 李华
网站建设 2026/8/14 21:37:42

ComfyUI-Manager实战指南:从安装配置到深度优化的全面教程

ComfyUI-Manager实战指南&#xff1a;从安装配置到深度优化的全面教程 【免费下载链接】ComfyUI-Manager ComfyUI-Manager is an extension designed to enhance the usability of ComfyUI. It offers management functions to install, remove, disable, and enable various c…

作者头像 李华
网站建设 2026/8/14 21:30:58

Mac本地部署大模型:2GB内存运行26B参数Gemma 2的SPAN优化引擎实践

这次我们来看一个在 Mac 上跑大模型的开源项目。它最吸引人的地方是&#xff0c;能在 M 系列 Mac 的 2GB 内存里&#xff0c;运行一个 260 亿参数的 Gemma 2 模型。这听起来有点不可思议&#xff0c;毕竟 26B 模型通常需要几十 GB 的显存。这个项目的核心价值在于&#xff0c;它…

作者头像 李华
网站建设 2026/8/14 21:29:44

Python正则表达式实战:解析和验证香港身份证、车牌、电话格式

文章目录 环境信息前言&#xff1a;正则能匹配"格式"&#xff0c;但匹配不了"真假"一、香港身份证号码的格式&#xff1a;三层结构二、校验码算法&#xff1a;mod 11 加权三、香港车牌格式&#xff1a;传统 vs 自定义四、香港电话号码格式五、完整验证器&a…

作者头像 李华