Java并发进阶系列:深度讨论高并发跳表数据结构ConcurrentSkipListMap的源代码实现(下)

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

Java并发进阶系列:深度讨论高并发跳表数据结构ConcurrentSkipListMap的源代码实现(下)
文章说明:因为CSM解析内容较多,因此全文分为“深度讨论高并发跳表数据结构ConcurrentSkipListMap的源代码实现(上)”和“深度讨论高并发跳表数据结构ConcurrentSkipListMap的源代码实现(下)”两篇文章上篇:CSM数据结构设计原理、doGet、doPut核心方法解析下篇:doRemove核心方法解析、总结remove方法:删除操作的设计原理这里先介绍Doug Lea在源代码注释给出算法设计说明:n.helpDelete(b,f)的设计原理上一篇文章的doGet、doPut方法中都有涉及到遇到被标记”删除“的节点时都会加入n.helpDelete(b,f)的处理逻辑,* In addition to using deletion markers, the lists also use * nullness of value fields to indicate deletion, in a style * similar to typical lazy-deletion schemes. If a node's value is * null, then it is considered logically deleted and ignored even * though it is still reachable. This maintains proper control of * concurrent replace vs delete operations -- an attempted replace * must fail if a delete beat it by nulling field, and a delete * must return the last non-null value held in the field. (Note: * Null, rather than some special marker, is used for value fields * here because it just so happens to mesh with the Map API * requirement that method get returns null if there is no * mapping, which allows nodes to remain concurrently readable * even when deleted. Using any other marker value here would be * messy at best.) * n是当前要删除的节点,b是n的前驱节点,f是n的后继节点,开始删除前,(b,n,f)的指向关系如下 * Here's the sequence of events for a deletion of node n with * predecessor b and successor f, initially: * * +------+ +------+ +------+ * ... | b |------| n |-----| f | ... * +------+ +------+ +------+ * * 1. CAS n's value field from non-null to null. * From this point on, no public operations encountering * the node consider this mapping to exist. However, other * ongoing insertions and deletions might still modify * n's next pointer. * * 1、 将删除节点n的value使用cas设成null,表示此刻起,n节点处于待删除状态,虽然其value为null,对于public相关方法如get等,在执行遇到此类型节点时不会将该节点视为有效节点(也即会被跳过处理),但是如果对于正在执行的插入和删除操作的方法来说,可能也可以去更改“此待删除节点n”的后继节点。 * 2. CAS n's next pointer to point to a new marker node. * From this point on, no other nodes can be appended to n. * which avoids deletion errors in CAS-based linked lists. * * +------+ +------+ +------+ +------+ * ... | b |------| n |-----|marker|------| f | ... * +------+ +------+ +------+ +------+ * 2、使用cas将n节点的next字段指向一个marker节点后,从此刻起,任何节点都不会被放在n的后继节点位置,也即不可能出现n-n1、n-n2等,只有n-marker * 3. CAS b's next pointer over both n and its marker. * From this point on, no new traversals will encounter n, * and it can eventually be GCed. * +------+ +------+ * ... | b |-----------------------------------| f | ... * +------+ +------+ * 3、通过cas将b的next指针(越过n节点以及n的后继marker节点)指向f节点,从此刻起,n节点不会被相关操作遍历到,n最终会被GC。可以看出CSM删除操作方面,借用marker节点来实现,将待删除节点的value设为null值来表示该节点处在删除状态但还未真正删除,这种设计风格就像“惰性删除语义(lazy-deletion schemes)”,先标记删,等某个时机再真正执行之。如果CSM的一个节点的value是null,说明该节点在逻辑上已经被删除。以下是remove方法的源代码解析publicVremove(Objectkey){returndoRemove(key,null);//注意只需要比较key即可,不需要考虑value相等才删除!}具体逻辑由doRemove实现finalVdoRemove(Objectkey,Objectvalue){if(key==null)thrownewNullPointerException();Comparator?superKcmp=comparator;outer:for(;;){// 1、和doGet、findNode类似设计,首先找到key的前驱节点for(NodeK,Vb=findPredecessor(key,cmp),n=b.next;;){//Objectv;intc;if(n==null)breakouter;// 经典的(b,n,f)三指针NodeK,Vf=n.next;// 2、n已不是b的后继节点,也即读n节点前后不一致,则重试if(n!=b.next)// inconsistent readbreak;// 3、数据节点n节点被标记为删除状态,那么使用helpDelete把n节点删除,然后重试。if((v=n.value)==null){// n is deletedn.helpDelete(b,f);break;}// 4、key的前驱节点b被标记删除状态,只能重试,读取新的bif(b.value==null||v==n)// b is deletedbreak;//5、 给定的key不在数据链表里面,直接结束if((c=cpr(cmp,key,n.key))0)breakouter;//6、 给定的key比当前数据节点n还大,那么更新b、n向右继续检索if(c0){b=n;n=f;continue;}// 以下找到与key相等的n节点//7、doRemove的入参value默认是null,因此以下会被跳过,若指定value,则还需判断给定的value和当前找到n.value是否相等if(value!=null!value.equals(v))breakouter;//8、执行流运行到这里,说明满足删除n节点的条件也即key=n.key,n就是要删除的目标数据节点,因此将n.value设为null,cas失败则重试,注意这是是标记删除,不是直接把n节点删除。if(!n.casValue(v,null))break;/*9、执行流运行到这里,说明n成功被标记删除状态 条件1:将 b → n → f 变成 b → n → marker → f 条件2:将 b → n → f 变成 b → f 如果条件1CAS失败或者条件2CAS失败,都会调用findNode(key)来删除数据节点n */if(!n.appendMarker(f)||!b.casNext(n,f))findNode(key);// retry via findNodeelse{/*10、不妨假设条件1成立,也即将 b → n → f 变成 b → n → marker → f 需要n节点上方的索引节点都清除(包括清除索引节点的前后指向关系),恰好findPredecessor就是在索引层干这事。 */findPredecessor(key,cmp);// clean indexif(head.right==null)tryReduceLevel();}@SuppressWarnings("unchecked")Vvv=(V)v;returnvv;}}returnnull;}虽然在doGet方法中有给出findPredecessor,它是用在查询场景中,而在本小节中findPredecessor被用来删除n节点对应的上方索引节点场景中,现在结合上方doRmove的源代码理解,将更能掌握其中设计逻辑。privateNodeK,VfindPredecessor(Objectkey,Comparator?superKcmp){if(key==null)thrownewNullPointerException();// don't postpone errorsfor(;;){for(IndexK,Vq=head,r=q.right,d;;){//在当前索引层检索,只要不是来到链表尾部,就继续在该层里面遍历if(r!=null){NodeK,Vn=r.node;Kk=n.key;// 此处对应的是上方doRemove内部将n.value标记为null的情况if(n.value==null){/* 既然数据层的n节点被删除,那么n节点对应上方关联的索引节点r也要删除: 索引层:将 q → r → r.right 变成 q → r.right */if(!q.unlink(r))break;// restart// 索引节点r成功删除后,将r指向q的新right节点,此时q → r 两个索引节点都处于正常状态,继续下一轮遍历。r=q.right;// reread rcontinue;}// 继续在该层索引向右检索,直到cpr(cmp, key, k) =0if(cpr(cmp,key,k)0){q=r;r=r.right;continue;}}// 当q位于第1层索引层位置时,此时q.down指向的是idx=null,因此可退出,到此从入口head到出口q.down=null沿途找到的与n对应的每层索引节点idx都被删除掉。可直接退出if((d=q.down)==null)returnq.node;// 执行流运行到这里,说明还未到达第1层索引层,以上逻辑完成当前层的idx节点删除,那么继续需要处理n节点对应的更低层的索引节点idx。q=d;r=d.right;}}}因此在doRemove方法的角度来看, findPredecessor(key, cmp) 实际意义是单纯用于clean index,而不是为key找到前驱节点b这么简单,这里的clean index功能就是Doug Lea提到的“side-effect”,也即下面这句话的含义:Callers rely on this side-effect of clearing indices to deleted nodes.doRemove作为调用方,能够从调用findPredecessor过程获得额外收益:清除那些已被标记为“删除状态”的数据节点上方对应的每层索引节点idx。关于doRemove的图解这里不再给出,想要深入理解的同学务必自行将删除逻辑对应的图做出来,否则将难以理解其过程的动态处理。tryReduceLevel()方法在doRemove方法的第10个逻辑中,清理完索引节点后,还需要检查是否需要“降层处理”:else{/*10、不妨假设条件1成立,也即将 b → n → f 变成 b → n → marker → f 需要n节点上方的索引节点都清除(包括清除索引节点的前后指向关系),恰好findPredecessor就是在索引层干这事。 */findPredecessor(key,cmp);// clean indexif(head.right==null)tryReduceLevel();

相关新闻

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

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

2026/7/28 11:17:33

在文章中《深度解析官方关于jdk1.8的resizeStamp的bug处理过程》,我们讨论关于CHM的核心设计——resizeStam需要修复的处理过程,本文再次基于openJDK的bugs讨论组提出的CHM源代码另外一个会造成死循环的bug,默认读者已经掌握CHM的核心源代码实现,否则无法从本文的讨论中获益…

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微控制器的组合,为解决这一难题提供了可靠的技术方案。这套方案特别适用于需要高可靠…

《创世战车》Darling角色攻略:辅助定位与团队协作技巧

《创世战车》Darling角色攻略:辅助定位与团队协作技巧

2026/7/28 12:17:36

这次我们来看《创世战车》中的JBRider甜心攻略,重点解析Darling角色的最佳玩法。如果你在团队战中经常因为操作不当被队友吐槽,或者想要提升Darling的实战贡献,这篇攻略可以直接收藏。Darling作为游戏中的辅助型角色,核心价值在于…

轻量级WebSocket服务器实现:从原理到实战部署

轻量级WebSocket服务器实现:从原理到实战部署

2026/7/28 12:17:36

1. 项目概述与核心价值 如果你正在寻找一个能快速上手、轻量级且功能纯粹的WebSocket服务器实现,那么Simple-WebSocket-Server绝对值得你花时间研究。这个开源项目,正如其名,它不追求大而全的框架级功能,而是聚焦于提供一个清晰、…

如何免费获得7种粗细的思源宋体:新手必备的中文排版全攻略

如何免费获得7种粗细的思源宋体:新手必备的中文排版全攻略

2026/7/28 12:17:36

如何免费获得7种粗细的思源宋体:新手必备的中文排版全攻略 【免费下载链接】source-han-serif-ttf Source Han Serif TTF 项目地址: https://gitcode.com/gh_mirrors/so/source-han-serif-ttf 还在为中文设计寻找完美的免费字体吗?思源宋体TTF字体…

噪声记录仪:噪声记录仪在DMA分区计量中的科学布点与间距策略

噪声记录仪:噪声记录仪在DMA分区计量中的科学布点与间距策略

2026/7/28 12:17:36

引言随着城镇化进程的加速和水资源日益紧缺,供水管网漏损已成为全球水务行业面临的严峻挑战。漏损不仅造成宝贵水资源的浪费,也增加了供水企业的运营成本,甚至可能引发次生灾害,影响城市安全运行。在此背景下,智慧水务…

MySQL数据分析实战:从零基础到独立完成电商业务分析报告

MySQL数据分析实战:从零基础到独立完成电商业务分析报告

2026/7/28 12:17:36

你是不是也遇到过这样的困惑:想学数据分析,网上铺天盖地的教程都在讲Python、Pandas、各种复杂的可视化工具,结果一上手,连最基础的数据都取不出来?或者,你发现公司里大量的业务数据其实就躺在MySQL数据库里…

如何在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是《杀戮尖塔》游戏中最强大、最安全的模组加载器,…

[具身智能-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)响应异常:语义明确的指令被忽略、中英…