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

Java并发进阶系列:jdk1.8 ConcurrentHashMap的TreeBin读写锁竞争机制讨论

本文接前面ConcurrentHashMap文章的内容,继续深入到TreeBin这个特殊节点的读写锁竞争机制。

7、TreeBin类设计原理

对TreeBin类深入分析,不仅能够理解为何CHM能支持并发读的底层实现,而且也能加深jk1.8的ConcurrentHashMap整体设计原理。本文将详细深入地解析TreeBin优秀的读写锁控制设计,此部分内容在全网的相关文章很少涉及。

7.1 保证加锁对象不改变的设计思想

首先看其源码的注释说明:

TreeNodes used at the heads of bins. TreeBins do not hold user keys or values, but instead point to list of TreeNodes and their root. They also maintain a parasitic read-write lock forcing writers (who hold bin lock) to wait for readers (who do not) to complete before tree restructuring operations.

TreeBins节点不是用于放置key和value,而是用于指向TreeNodes和TreeNodes的根节点。TreeBins内部会维护一把读写锁,用于保证在树重构前,持有锁的写线程被强制等待无锁读线程完成。

当然这注释也未能回答这样核心问题:为何对于桶位是红黑树的情况下,CHM放置的一个TreeBin节点,而不像HashMap那样放置一个TreeNode节点?

首先看看TreeBin的使用场景,在put场景下:

synchronized(f){if(tabAt(tab,i)==f){//if(fh>=0){// ..}//elseif(finstanceofTreeBin){Node<K,V>p;binCount=2;if((p=((TreeBin<K,V>)f).putTreeVal(hash,key,value))!=null){oldVal=p.val;if(!onlyIfAbsent)p.val=value;}}}}

当key所在桶位可以放入key时,先对头节点加锁synchronized (f),注意这是独占锁,要求头节点对象在锁期间不会改变,否则就不能锁住同一对象。

为论证f头节点是TreeNode类型不适用并发的CHM场景,不妨假设CHM使用TreeNode作为CHM桶位上红黑树的头节点,于是在put场景下:

// ① 假设当前头节点f是一个TreeNode节点,则执行流会进入②分支synchronized(f){if(tabAt(tab,i)==f){//if(fh>=0){// ..}// ② 这里已经将TreeBin换成TreeNode类型elseif(finstanceofTreeNode){Node<K,V>p;binCount=2;// ③ 将key和value插入到红黑树当中if((p=((TreeBin<K,V>)f).putTreeVal(hash,key,value))!=null){oldVal=p.val;if(!onlyIfAbsent)p.val=value;}}}}

这里关键是第③步逻辑:将key和value插入到红黑树当中,会发生什么事情?

我们知道,在HashMap节点,将一个节点put入红黑树后,需要做插入平衡处理和将红黑树root节点移到双向链表的头节点位置,目的是为了保证桶位上的头节点即是红黑树的根节点也是双向链表的头结点,下面就是HashMap对应的操作

finalTreeNode<K,V>putTreeVal(HashMap<K,V>map,Node<K,V>[]tab,inth,Kk,Vv){// 插入平衡处理和将红黑树root节点移到双向链表的头节点位置moveRootToFront(tab,balanceInsertion(root,x));returnnull;}}

但对于并发CHM来说,红黑树的插入平衡处理会导致root节点发生了改变(例如插入平衡前根节点是treeNodeA,插入平衡后根节点是treeNodeB)而不是位于桶位头节点上,如果CHM桶位头节点还是TreeNode,那么就会出现以下图示的不能保证写线程独占操作的线程不安全情况。

如何解决以上遇到的问题?

Doug Lea为此设计了TreeBin类(节点),该节点只放在桶位头节点上,它内部“包装”一棵红黑树(有些文章会跟你说TreeBin是 TreeNode的代理结点,其实意思都一样)。加锁时,对桶位头节点上TreeBin节点进行加锁,内部的红黑树根节点root在调整平衡后不管如何变化,当前桶位的加锁对象——TreeBin节点保持不变,也即保证写操作独占性,如下图所示。此外TreeBin还支持高级特性:并发环境下的读写锁控制机制。

而且这样设计的TreeBin还有另外一个收益:无需进行类似HashMap的moveRootToFront操作,因为桶头节点不再要求是

红黑树的根节点root,也不是链表的first头节点,而是一个包装了TreeNodes的TreeBin节点。

以下是HashMap的插入节点后需要做moveRootToFront操作的源码片段:

// putVal方法内部代码片段elseif(pinstanceofTreeNode)e=((TreeNode<K,V>)p).putTreeVal(this,tab,hash,key,value);// putTreeVal方法内部代码片段moveRootToFront(tab,balanceInsertion(root,x));

以下是CHM插入新节点后的插入平衡操作:在插入平衡操作前,采用cas加锁机制,显然不同于HashMap,这个加锁机制有点复杂,目的是什么呢?在后面7.3小节会提到。

else{lockRoot();try{// 仅有插入平衡操作,没有moveRootToFront操作,因为桶位头节点一直都是TreeBin节点,这个节点的hash值为-2,没有key和valueroot=balanceInsertion(root,x);}finally{unlockRoot();}}break;

此外,在源码中的lockRoot等地方的注释中,有个短语:tree restructuring,表示:红黑树重新调整结构。

而tree restructuring operations则表示:红黑树重新调整结构操作,具体来说是以下两个方法

put方面内部的putTreeVal方法以及remove方法内部的removeTreeNode方法,也即红黑树插入一个新节点需要触发tree restructuring 操作,红黑树删除一个节点需要触发tree restructuring 操作:执行tree restructuring操作前需要使用lockRoot()获取写锁,调整完后使用unlockRoot()释放写锁。

7.2 TreeBin类

TreeBin作为CHM内部类,有部分设计是新设计,用于解决高并发条件下的读写竞争问题,而另外一部分设计则沿用HashMap红黑树部分操作方法:如rotateLeftrotateRightbalanceInsertionbalanceDeletion等,这几个方法是在synchronized(f)独占锁条件系进行的,因此跟HashMap原来的执行机制类似,此处不再累赘。本章重点放在新的设计上。

7.2.1 基本的成员变量

基本成员变量具体使用在7.3小节的读写竞争设计给出,waiter、lockState以及lockState对应的三个标记值,再结合位运算方式,非常巧妙的实现了TreeBin读写锁竞争!

staticfinalclassTreeBin<K,V>extendsNode<K,V>{TreeNode<K,V>root;// 桶位上红黑树根节点的引用volatileTreeNode<K,V>first;// 因为TreeNode节点还有next属性,因此红黑树本身也是一条链表,first就是该链表的头节点,常常用在需要线性遍历节点的场景volatileThreadwaiter;// 参考7.3.1小节内容volatileintlockState;// 直译:锁状态,用于表征TreeBin对象当前的锁状态是什么,对应以下三种状态// values for lockStatestaticfinalintWRITER=1;// set while holding write lock 写线程给lockState进行cas设1以此获得写锁:0001staticfinalintWAITER=
http://www.jsqmd.com/news/1283175/

相关文章:

  • 基于微信小程序的社区疾病防控系统设计与实现
  • 独家:金融级AI沙箱隔离框架开源前夜(附Gartner认证的5层数据脱敏验证清单)
  • Linux运维实战入门:从零部署Nginx到Shell脚本自动化
  • 希尔伯特变换原理与工程实践全解析
  • 现代C++ JSON库nlohmann/json:从配置到实战的完整指南
  • Video VAE 不是末端编解码器:5 维-潜网格-主模型 Token 的“表示合同“
  • 125、K210的摄像头图像分类案例
  • 即梦去水印怎么去掉?2026实测3个免费解析方法 - 耶斯去水印
  • Java并发进阶系列:深度讨论jdk1.8 ConcurrentHashMap并发环境下transfer方法桶位分配过程
  • 【工具-Visual Studio Code】
  • 洛阳前锋热水器售后维修电话全新专属升级公告 - 科技先行者
  • 2026年7月水泥制品厂家有哪些,化粪池/水泥制品消防井/成品检查井/水泥制品隔油池/水泥制品雨水井,水泥制品工厂推荐 - 品牌推荐师
  • 2026 年新消息:金平知名的管网抢修疏通企业哪家靠谱,楼下的下水道突然冒臭水?别慌,这玩意儿半小时就能搞定!-三禾市政工程 - 企业官方推荐【认证】
  • 广州番禺区搬家公司怎么选?健身房搬家收费明细,跑步机杠铃重型器械搬运流程、真实案例与选择指南 - 厚道搬家
  • LM2596、MP2307与数控降压模块实测对比:效率、纹波与选型指南
  • 抖音批量下载终极指南:3个超简单步骤掌握无水印视频批量保存技巧
  • 正式推出洛阳华生热水器售后维修电话全新专属升级公告 - 科技先行者
  • 星火应用商店完整指南:5个技巧让Linux软件管理变得简单
  • SonarQube误报调优实战:从规则冲突到精准豁免
  • 曲柄压力机的离合器和制动系统设计
  • 植物大战僵尸杂交版(最新版)
  • PolarDB(阿里云)VS HaishanDB(海山数据库,移动云)的AI能力全面对比
  • C++ std::list深度解析:双向链表原理、性能对比与高效应用场景
  • 节假日机票太贵?掌握方法,教你怎么买便宜机票轻松出行 - 工具软件使用方法推荐
  • 【AI课程笔记整理黄金法则】:20年AI教育专家亲授,97%学员忽略的5个致命误区
  • QT自定义控件之化学工艺流程图
  • 天河珠江新城大平层精细化搬家计费方式,全屋收纳打包入户复位完整服务案例解析 - 厚道搬家
  • 抖音图文无水印保存方法详解 2026合规教程与工具风险提醒 - 免费软件工具方法教程
  • IPD中的扫地僧(TDT技术开发团队),都在扫什么?
  • NGC_综述_导航制导与控制