Java并发进阶系列:深度讨论官方关于jdk1.8ConcurrentHashMap的computeIfAbsent源代码修复逻辑

发布时间:2026/7/28 11:17:33

Java并发进阶系列:深度讨论官方关于jdk1.8ConcurrentHashMap的computeIfAbsent源代码修复逻辑
在文章中《深度解析官方关于jdk1.8的resizeStamp的bug处理过程》,我们讨论关于CHM的核心设计——resizeStam需要修复的处理过程,本文再次基于openJDK的bugs讨论组提出的CHM源代码另外一个会造成死循环的bug,默认读者已经掌握CHM的核心源代码实现,否则无法从本文的讨论中获益。文章前部分先把computeIfAbsent的bug成因分析清楚,再来介绍官网ConcurrentHashMap.computeIfAbsent stuck in an endless loop的讨论过程,这样更容易看懂相关内容。研究openJDK官方公布的相关源码bug有何“收益”:虽然这些bug不是特别严重,修复起来也即几行代码,但如果想要解决这种看似“简单的bug”,要求对CHM设计原理、类、方法实现细节足够熟悉,也就是说,你要具备(至少在这个bug上下文的类、方法范围内)和源代码设计者同等思考视角才能去挖掘bug的本质原因并提出合理的修复建议。换句话说,你研究的不是这个bug本身,而是深入精通整个类的源代码实现,这种高级收益在日常业务开发几乎无法获得。认识computeIfAbsent用法理解computeIfAbsent在一些场合下的用法,有助于帮助切入源代码分析。computeIfAbsent使用场景1:packageconcurrent.demo;importjava.util.concurrent.ConcurrentHashMap;publicclassDemo1{staticintcomputeKeyLength(Stringkey){// 计算key的长度,将其作为该key对应的valuereturnkey.length();}publicstaticvoidmain(String[]args){ConcurrentHashMapString,Integermap=newConcurrentHashMap();map.put("foo",1);map.computeIfAbsent("foobar",key-computeKeyLength(key));System.out.println(map);//输出 {foobar=6, foo=1}}computeIfAbsent字面意思:如果key不在map里面,那么就使用给定的匿名函数(也叫映射函数)将key对应的value“计算出来”。(匿名函数也即lambda语法是jdk1.8语法新特性,这一点不必多说)按这个思路可以有以下解释:因为字符串"foobar"这个key不在map里面,因此把它放入map的同时,对应的value要用给定的函数computeKeyLength计算出来,例如这里调用computeKeyLength计算值为6,因此有key=foobar,value=6,将其放入map中。map.put("bar",10);map.computeIfAbsent("bar",key-computeKeyLength(key));System.out.println(map);// 输出:{bar=10}若key已经在map时,value不会被computeKeyLength(key)的计算值6所覆盖。当然此demo做了一个不优雅的示范:既然可用匿名函数的写法去写逻辑,就没必要基于方法computeKeyLength去封装多一层,最简便写法如下:map.computeIfAbsent("foobar",key-key.length());注意这个key可以作为匿名函数的入参去参与到计算value,也可以不作为匿名函数的入参,如下:map.computeIfAbsent("foobar",key-10);显然foobar=10。computeIfAbsent使用场景2:并发场景下的频率统计:该demo方法其实在并发计数器LongAdder这个类的源码注释里面,Doug Lea已经告诉我们一个经典的场景恰好需要使用computeIfAbsent方法LongAdders can be used with a java.util.concurrent.ConcurrentHashMap to maintain a scalable frequency map (a form of histogram or multiset). For example, to add a count to a ConcurrentHashMapString,LongAdder freqs, initializing if not already present, you can use freqs.computeIfAbsent(k - new LongAdder()).increment();packageconcurrent.demo;importjava.util.concurrent.ConcurrentHashMap;importjava.util.concurrent.atomic.LongAdder;publicclassDemo2{publicstaticvoidmain(String[]args){ConcurrentHashMapString,LongAddermap=newConcurrentHashMap();String[]strings={"foo","bar","foo","foo"};for(Stringkey:strings){map.computeIfAbsent(key,k-newLongAdder()).increment();}System.out.println(map);//输出 {bar=1, foo=3}}}该demo虽然只是用单个线程去执行computeIfAbsent,但逻辑是清晰的:实现对字符串出现次数进行统计关于LongAdder的分析,它内部其实有一个像ConcurrentHashMap的fullAddCount并发计数逻辑,这里不再讨论,有关研究可参考本博客的文章《Java并发进阶系列:LongAdder高并发计数性能分析》computeIfAbsent方法源码解析这部内容要求读者已经掌握jdk1.8的ConcurrentHashMap设计及其关键方法的源代码实现逻辑,否则将难以理解其含义。本节所提的computeIfAbsent是未修复前的版本,这里并不会详细解析computeIfAbsent每一行代码,因为它跟putVal方法逻辑几乎一样,而不同地方可参考以下数字标记的说明:publicVcomputeIfAbsent(Kkey,Function?superK,?extendsVmappingFunction){if(key==null||mappingFunction==null)thrownewNullPointerException()inth=spread(key.hashCode());Vval=null;intbinCount=0;for(NodeK,V[]tab=table;;){//看到这个写法应该很熟悉了:自旋+cas机制,为啥要自旋,因为线程不保证自己一次cas就成功,如果和其他线程竞争失败,则需要重试cas,这就是“自旋+cas机制”的黄金搭配。NodeK,Vf;intn,i,fh;if(tab==null||(n=tab.length)==0)tab=initTable();elseif((f=tabAt(tab,i=(n-1)h))==null){//① 如果key对应的桶位为空,先创建一个保留节点用于接下里的占位逻辑NodeK,Vr=newReservationNodeK,V();// ②当前线程用保留节点占位当然需要借用独占锁对r对象进行加锁synchronized(r){// 在当前桶位放置保留节点用于占位,占位之后就可以给该桶位放入新建的node节点if(casTabAt(tab,i,null,r)){binCount=1;NodeK,Vnode=null;try{/*putVal在桶位为空时的逻辑,可看到非常简单,直接使用cas给当前桶位设置新节点,value是给定的value,不需要通过函数计算出value if (casTabAt(tab, i, null,new NodeK,V(hash, key, value, null))) { break; } */// 对于computeIfAbsent,value是需要用给定的匿名函数计算出的,正如前面场景1的“bar”这个key对应的“value”就是使用computeKeyLength(key)计算处理的值if((val=mappingFunction.apply(key))!=null)node=newNodeK,V(h,key,val,null);}finally{//虽然在②步骤那里已经在桶位i放置了一个ReservationNode用于占位,到了这个步骤才是真正把数据节点node放入桶位i当中setTabAt(tab,i,node);}}}// 显然②步骤一定能成功在桶位i放入node节点(binCount=1),既然已经将key和value放入map,那么任务完成,当前线程退出自旋if(binCount!=0)break;}//③如果key定位到的桶位i恰好是一个ForwardingNode占位节点,那么当前线程要去参与“帮助扩容”的逻辑,这里跟putval一样。elseif((fh=f.hash)==MOVED)tab=helpTransfer(tab,f);else{//代码若执行到这,说明桶位i是一个链表或者一棵红色树booleanadded=false;// 当前线程先给头节点加独占锁,保证当前线程写入节点操作时的独占性synchronized(f){//并发环境,这里当然还要二次检查头节点是不是刚刚加锁前的头节点(也即检查加锁前后的头节点有无被改动过)if(tabAt(tab,i)==f){// ④f节点是链表的情况if(fh=0){binCount=1;for(NodeK,Ve=f;;++binCount){Kek;Vev;// 在链表中遇到相同的key,那么就不做更新value操作,返回旧valueif(e.hash==h((ek=e.key)==key||(ek!=nullkey.equals(ek)))){val=e.val;break;}NodeK,Vpred=e;// 尾插法:来到链表尾部if((e=e.next)==

相关新闻

Java进阶系列:深度解析jdk1.8的HashMap红黑树balanceDeletion节点删除平衡算法设计(核心文章)

Java进阶系列:深度解析jdk1.8的HashMap红黑树balanceDeletion节点删除平衡算法设计(核心文章)

2026/7/28 11:17:33

这可能是全网最期待的jdk1.8的红黑树balanceDeletion的源代码解析技术文章! 其实掌握HashMap红黑树的同学都知道,balanceDeletion方法的源代码是HashMap红黑树部分最复杂也是最难理解的部分,目前少有coder对balanceDeletion有足够深入且可理解的分析,绝大部分关于深入Hash…

工业级负载控制方案:TPD2015FN与MKV42F128VLH16应用解析

工业级负载控制方案:TPD2015FN与MKV42F128VLH16应用解析

2026/7/28 11:17:33

1. 工业级负载控制方案概述在工业自动化、电力电子和高端设备控制领域,对电感和电阻负载的精确控制一直是工程师面临的核心挑战。TPD2015FN智能功率IC与MKV42F128VLH16微控制器的组合,为解决这一难题提供了可靠的技术方案。这套方案特别适用于需要高可靠…

物联网安全升级:SE050安全元件与TM4C1294NCPDT的完美结合

物联网安全升级:SE050安全元件与TM4C1294NCPDT的完美结合

2026/7/28 11:07:33

1. 物联网安全现状与SE050的定位在2023年的物联网安全态势报告中,全球每天新增的物联网设备达到惊人的150万台,但其中近70%的设备存在中高危安全漏洞。传统MCU方案(如STM32系列)虽然成本低廉,但在密钥存储、安全启动、…

如何在5分钟内完成ModTheSpire游戏模组加载器的完整配置指南

如何在5分钟内完成ModTheSpire游戏模组加载器的完整配置指南

2026/7/28 12:07:35

如何在5分钟内完成ModTheSpire游戏模组加载器的完整配置指南 【免费下载链接】ModTheSpire External mod loader for Slay The Spire 项目地址: https://gitcode.com/gh_mirrors/mo/ModTheSpire ModTheSpire是《杀戮尖塔》游戏中最强大、最安全的模组加载器,…

faster-whisper-GUI:免费开源的语音转文字终极解决方案

faster-whisper-GUI:免费开源的语音转文字终极解决方案

2026/7/28 12:07:35

faster-whisper-GUI:免费开源的语音转文字终极解决方案 【免费下载链接】faster-whisper-GUI faster_whisper GUI with PySide6 项目地址: https://gitcode.com/gh_mirrors/fa/faster-whisper-GUI 你是否曾为会议录音整理而烦恼?是否在视频字幕制…

Spring MVC拦截器原理与性能优化实战

Spring MVC拦截器原理与性能优化实战

2026/7/28 12:07:35

1. Spring MVC拦截器核心原理剖析 当面试官抛出"说说Spring MVC拦截器的原理"这个问题时,他实际上在考察你对Spring框架请求处理流程的底层认知。作为Spring MVC的核心扩展点之一,拦截器(Interceptor)的设计体现了典型的…

【JAVA毕业设计】基于 SpringBoot+Vue 的轻量化校园闲置交易与订单溯源系统 高校师生二手物品互换买卖服务平台(源码+文档+远程调试,全bao定制等)

【JAVA毕业设计】基于 SpringBoot+Vue 的轻量化校园闲置交易与订单溯源系统 高校师生二手物品互换买卖服务平台(源码+文档+远程调试,全bao定制等)

2026/7/28 12:07:35

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

企业AI应用Token管理优化:从分配机制到技术实践

企业AI应用Token管理优化:从分配机制到技术实践

2026/7/28 12:07:35

在企业数字化转型和AI应用落地的浪潮中,Token管理正成为技术团队面临的核心挑战之一。很多团队误以为Token消耗过快是预算不足的问题,但实际上,这往往源于更深层次的分配机制不合理。本文将深入分析企业Token消耗的真实痛点,并提供…

C++与g2o实现最小二乘曲线拟合:从原理到工程实践

C++与g2o实现最小二乘曲线拟合:从原理到工程实践

2026/7/28 11:57:35

1. 项目概述与核心价值 最近在整理一个机器人轨迹优化的项目,里面涉及到大量的传感器数据对齐问题。传感器数据点往往带有噪声,直接拿来用效果很差,这时候就需要一个“平滑器”来从一堆杂乱的点里,找到那条最有可能的真实轨迹。这…

[具身智能-649]:个人电脑搭建 RTSP 服务完整方案(Windows / Ubuntu 双平台,适配 RDK X5 rtsp2display 调试)

[具身智能-649]:个人电脑搭建 RTSP 服务完整方案(Windows / Ubuntu 双平台,适配 RDK X5 rtsp2display 调试)

2026/7/27 8:45:59

目标:电脑作为RTSP 服务端,循环推送 H264/H265 视频流; RDK X5 通过 rtsp2display 拉流预览,完全不需要在开发板编译 live555。 提供两套成熟方案: ✅ 方案 A:FFmpeg(最简单,优先推…

PDF合并与动态水印的工程化方案:2026国内免费工具实测对比

PDF合并与动态水印的工程化方案:2026国内免费工具实测对比

2026/7/27 8:42:17

一、背景与测试方案 在实际项目交付中,PDF文件合并与版权保护水印的叠加是一个高频但容易被低估的技术需求。典型的处理链路涉及:多源PDF的文件流合并、页面级水印渲染(含透明度混合与图层叠加)、输出文件体积控制。看似简单的操作…

PDF拆分压完图糊了?2026国内免费实测,档案员都在用的组合方案

PDF拆分压完图糊了?2026国内免费实测,档案员都在用的组合方案

2026/7/27 14:56:57

说实话,提到PDF拆分再压缩,我真是被折腾得够呛。 上个月公司年度合同归档,一份300多页的PDF总合同,需要按年份拆分成三个独立文件,再分别压缩到10MB以内方便邮件发送各部门确认。我心想这还不简单?先找个海…

零基础搭建桌面智能体,OpenClaw 2.7.9 分步实操,避开绝大多数部署陷阱

零基础搭建桌面智能体,OpenClaw 2.7.9 分步实操,避开绝大多数部署陷阱

2026/7/28 0:06:55

📌 一、工具核心优势盘点 数据本地存储,安全系数高所有操作日志、文档资料均保存在本机,不会上传至云端,能够有效保护企业文件与个人隐私,规避数据泄露风险。 上手简单,零编程门槛采用全图形化可视化界面&…

计算机毕业设计之基于springboot的购物平台设计与实现

计算机毕业设计之基于springboot的购物平台设计与实现

2026/7/28 0:06:55

由于移动应用技术的持续性的快速发展,现实生活中人们大多数都是通过移动手机、电脑等智能设备来完成生活中的事务。因此,许多的人工传统行业也开始与互联网结合,不再一味的依靠人工手动,努力打造半自动数字化甚至是全自动数字化模…

豆包AI绘图提示词失效真相:NLP模型层token截断机制首次披露,3招绕过字数限制

豆包AI绘图提示词失效真相:NLP模型层token截断机制首次披露,3招绕过字数限制

2026/7/28 0:06:55

更多请点击: https://codechina.net 第一章:豆包AI绘图提示词失效现象全景扫描 近期大量用户反馈,豆包(Doubao)AI绘图功能对常规提示词(Prompt)响应异常:语义明确的指令被忽略、中英…