当前位置: 首页 > news >正文

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 里。
http://www.cnnetsun.cn/news/4028748.html

相关文章:

  • 一个下午搬走 800 条高清视频:免费开源的抖音无水印下载工具 douyin-downloader 使用全记录
  • 从零制作百度输入法皮肤:双色主题设计与跨平台适配全攻略
  • ComfyUI-Manager实战指南:从安装配置到深度优化的全面教程
  • Mac本地部署大模型:2GB内存运行26B参数Gemma 2的SPAN优化引擎实践
  • Python正则表达式实战:解析和验证香港身份证、车牌、电话格式
  • 材料发现基准测试:大语言模型为半导体寻材有成果,但也存在可合成性等问题!
  • Kodi 字幕下载太折腾?这款免费字幕插件 5 分钟解决找字幕难题
  • 26MHz热敏晶振换料实战:手机无线子板从泰晶切换到鸿星的选型与验证记录
  • Stable Diffusion入门第16-18天:从文生图到图生图——用一张图“导演”你的AI创作
  • AI搜索技术解析:从Gemini模型到工程落地实践
  • 基于多智能体协作的AI绘画:GPT-Image-2 Skill与Hermes框架实战
  • EdgeRemover:彻底移除 Edge 的终极指南
  • AI数字化蛋白筛选到底是否靠谱?AI-PPI/多模态/虚拟筛选一篇汇总!
  • 深入解析vLLM:PagedAttention如何革新LLM推理的显存管理与吞吐量
  • 想做测试工具却不会写代码?怎么办?
  • 文生TikZ
  • 国家工程技术研究中心申报条件有哪些
  • Python Django电商比价系统:从爬虫到智能Agent的全栈实践
  • 智能代码搜索:从向量化到混合检索,揭秘“离谱快”背后的技术架构
  • RTX Spark:在个人PC上搭建GPU加速的Spark大数据与AI开发环境
  • 三步装好Android Studio中文语言包,开发界面5分钟变中文
  • SH9持续同调拓扑正则性与无导数奇点检测:拓扑表示对应等价条件与Navier-Stokes奇异性的拓扑刻画
  • 终极指南:如何使用Holehe进行邮箱账号安全检测
  • Jinq 社区精选:用户最常问的 20 个问题与解答
  • klogg日志分析终极指南:如何快速检索10GB级日志文件(安装、搜索与实时监控全攻略)
  • ElasticSearch Paramedic源码解析:Ember.js前端架构与ElasticSearch API交互原理
  • 微信聊天记录导出其实很简单:换手机前,我把三年对话永久存进了本地
  • 新手必看:R3PLAYX界面导航与基础操作完全指南
  • Minum框架扩展指南:从零开发自定义数据库引擎的完整教程
  • 高阶C++-SFINAE