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

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

前言

首先给出以下open JDK版本的序号说明和Oracle JDK序号说明

(1)对于JDK8或者Java 8

即可指代openjdk-8-jdk或者java-1.8.0-openjdk,

也可指代Oracle家的Java SE 8或者JDK 8u211 and later

(1)对于JDK16或者Java 16

即可指代openjdk的JDK 16.0.2 ,也可指代Oracle家的Java SE 16或者 jdk16.0.1,这里为何给出Java 8和Java 16版本说明?

首先resizeStamp的bug在Java8出现,并在Java 12被修复,因此本文直接给出最新版Java 16作为bug修复前后对比即可。

以下做个约定:统一以Java X形式作为版本称号,CHM:ConcurrentHashMap的简称,以此减少阅读障碍。

在前面的文章中,关于Java 8 的CHM addCount方法里面分支2:resizeStampsc==rs+1、sc==rs+MAX_RESIZERS的讨论中,已经指出其bug嫌疑:

privatefinalvoidaddCount(longx,intcheck){// 分支1 省略...// 分支2if(check>=0){Node<K,V>[]tab,nt;intn,sc;while(s>=(long)(sc=sizeCtl)&&(tab=table)!=null&&(n=tab.length)<MAXIMUM_CAPACITY){intrs=resizeStamp(n);// 注意这里计算出的rs是正值if(sc<0){// sc是负值,怎么会等于rs+1或者rs + MAX_RESIZERS这个正值呢? 有可能是个bugif((sc>>>RESIZE_STAMP_SHIFT)!=rs||sc==rs+1||sc==rs+MAX_RESIZERS||(nt=nextTable)==null||transferIndex<=0)break;if(U.compareAndSetInt(this,SIZECTL,sc,sc+1))transfer(tab,nt);}elseif(U.compareAndSetInt(this,SIZECTL,sc,(rs<<RESIZE_STAMP_SHIFT)+2))transfer(tab,null);s=sumCount();}}
官方bug描述

其实这个bug在open jdk的官方bugs主页已经给出相关解释和修复过程,链接:官方bug描述页面:

从Detail这一块描述得到信息如下:

bug的描述:ConcurrentHashMap.addCount()设计逻辑中可能存在bug。(这里虽然提到addCount()方法,但本人更想强调的是扩容分支的resizeStamp的bug)

级别是:bug

当前状态:已经修复

影响的版本:Java 11、Java12

在哪个版本得到修复:Java 12

使用操作系统平台:所有

bug页面创建时间:2018-11-26

解决bug的最后时间:2018-12-11

bug所属库:Java的核心库——core-libs

提交者的修复建议

In the above code, condition of (sc == rs + 1 || sc == rs + MAX_RESIZERS ) would never be true , since the value of rs is positive and the value of sc is negative .

译:条件 (sc == rs + 1 || sc == rs + MAX_RESIZERS )永远不可能true,因为rs的值为正数,而sc值为负数

并建议修改为:

The correct condition should be (sc >>> RESIZE_STAMP_SHIFT) == rs + 1 || (sc >>> RESIZE_STAMP_SHIFT) == rs + MAX_RESIZERS, which can be used to dedect if resizing process finished or resizing threads reaches maxmium limitation

译:正常的条件应该是这样: (sc >>> RESIZE_STAMP_SHIFT) == rs + 1 || (sc >>> RESIZE_STAMP_SHIFT) == rs + MAX_RESIZERS,这两个条件表示扩容任务已结束或者参与扩容的线程总数达到最大值

确实,这个bug非常明显,以分支2作为说明

int rs = resizeStamp(n),以容量n=16作为说明,rs计算为下面的值

0000 0000 0000 0000 1000 0000 0001 1011

考察低16位,rs+1的结果显然是一个正数

0000 0000 0000 0000 1000 0000 0001 1100

rs+MAX_RESIZERS同理也是一个正数,接着判断条件if(sc<0)成立才能进入rs+1等条件,也即此时sc是一个负数(其实是因为首个扩容线程会将sc设为(rs << RESIZE_STAMP_SHIFT) + 2的一个基础负数),基于此,有提交者在这里发现的了bug:sc是负数,而rs+1是正数,因此sc==rs+1永远不会成立

官方关于此bug的讨论过程

JCP JSR-166 Expert Group (关于Java并发编程的规范提案的专家组)几个相关成员的对话过程即可知道他们对问题的思考和处理方式。

在Activity这个栏目就是用于提交者已经相关专家bug讨论过程,“All”是显示所有他们的活动记录,一般无需关注,“Comments”显示他们的对话过程,bug的讨论过程就在这里,因此需要重点关注,具体如下:

以下是来自“Comments”区域的内容:

(最开始由Webbug Group 这个小组提交了该issue - 2018-11-26 00:53)

Stuart Marks 说:

Stuart Marks added a comment - 2018-11-28 09:10

Martin, can you take a look at this?

Martin,来,帮我看看这个bug?

Martin的回答,主要意思是:bug提交者在查一个确实是由addCount产生错误计数,但Martin说他们也没有可以使用的压测案例,并建议使用者用多线程做压测来让addCount的这个bug复现,但这个bug不好复现。

Resizing the internal bucket array is hairy race-prone code, and hard to stress test because resizes are relatively rare.

The reporter probably investigated an actual occurrence of incorrect count (we could ask!), but we don’t have a stress test reproduction that could be used.

One should be able to construct a stress test using multiple threads to trigger concurrent attempts to addCount, but it won’t be easy.

David Holmes 对Martin说:

What is your analysis just based on the code and the report? It certainly appears incorrect to me.

你的分析只是基于代码以及提交的报告?这个bug在我看来显然是不正确的。

Martin回答David Holmes :

Martin说自己也看了看源码但研究时间不够长,自己还没能搞懂其设计,然后说Doug应该记得这个设计!

I stared at the code for a while, but not long enough to understand it. Doug will remember!

Doug Lea added a comment - 2018-11-28 15:53:

Doug Lea看到这个bug,做了基本的分析:这个bug会影响到CHM性能也即有些线程不能参与到扩容任务中,并指出这个bug只是影响性能而不是一个引起map发生错误的bug,指出这个bug需要修复。

Yes. Some of this check now includes dead code, because of a change of representation at one point that wasn’t adjusted for. With the possible effect of some threads not helping resize (a performance, not map correctness bug) This should be fixed (and is committed in jdr166 repo):

然后他贴出修复前后的源码diff

---ConcurrentHashMap.java.~1.314.~2018-10-0513:42:39.860409607-0400+++ConcurrentHashMap.java2018-11-2818:48:55.998082379-0500@@-2307,9+2307,9@@(n=tab.length)<MAXIMUM_CAPACITY){intrs=resizeStamp(n);if(sc<0){-if((sc>>>RESIZE_STAMP_SHIFT
http://www.jsqmd.com/news/1280281/

相关文章:

  • 3分钟搞定Windows安卓子系统:WSABuilds让你的PC秒变安卓手机
  • 宏利工程师为您解读——Wafer连接器的核心构造与性能优势 - CindyYi
  • 2024-2026全网计算机软件毕业设计选题大全:不要踩坑了✅
  • 2026新版长春防水补漏服务商参考|阳台渗漏修缮方案指南 - 筑宅安
  • UGC活动运营全解析:从情绪共鸣到社区构建的实战方法论
  • 2026建筑修缮行业复杂渗漏治理新趋势:本地服务商的专业价值与实践路径 - 鼎万建筑修缮
  • 分布式事务解决方案:从原理到实践
  • RAG技术解析:破解大模型幻觉的实战指南
  • NBM7100A芯片在物联网设备中的电源管理优化方案
  • Wukong AICRM Docker部署指南:从一键启动到API集成与批量任务测试
  • 如何在PC上免费畅玩Switch游戏:yuzu模拟器终极避坑指南
  • 2026广州黄金回收迈入直熔合规时代,合扬打通本地精炼厂直收新标准 - 好物测评局
  • Ollama本地部署大语言模型:从入门到实践
  • 2026年郑州涉外律师豫企出海全周期安全合规实践 - 万相科技
  • TileLang终极指南:如何用Pythonic语法编写高性能GPU算子
  • ExifToolGUI免费开源工具:照片元数据管理终极解决方案
  • Zookeeper--08---zk实现分布式锁、案例
  • 2026长沙出售翡翠手镯好不好选易奢福,1公里1家门店,7区全覆盖 - 奢侈品回收实体店
  • 前端大厂面试总结大全
  • 英伟达双线出击:开源安全AI联盟与102.4T交换机,如何重构AI基础设施的“安全”与“速度”?
  • 字符串解码算法:栈的应用与实现详解
  • 2026年19元移动无限流量包代理公司性价比排名 - 信息热点
  • 物联网设备安全芯片SE050与PIC32MX675F512L集成方案
  • OpenClaw 实操部署手册,解决本地电脑自动化各类报错问题
  • Gemini 3.6 Flash多智能体框架在游戏设计中的实战应用
  • .NET 8配置系统与Serilog日志集成深度解析
  • 2026年河南ODI备案三大误区与合规破局之道 - 万相科技
  • 物联网安全芯片SE050与STM32的硬件级安全方案
  • 物联网设备安全芯片SE050的应用与STM32集成实战
  • 2026微信投票评选活动怎么制作?海投票小程序新手实操教程 - 微信投票小程序