Java并发编程:AQS对CLH锁的优化

Wesley13
• 阅读 344

自旋锁适用于锁占用时间短,即锁保护临界区很小的情景<AQS的自旋锁详解>。它需要保证各缓存数据的一致性,这可能会导致性能问题。因为在多处理器机器上每个线程对应的处理器都对同一个变量进行读写,而每次读写都要同步每个处理器的缓存。此外,自旋锁无法保证公平性,即不保证先到先获得,这就可能造成线程饥饿。

01

CHL锁

为了优化同步带来的花销,Craig、Landin、Hagersten三个人发明了CLH锁。其核心思想是:通过一定手段将所有线程对某一共享变量的轮询竞争转化为一个线程队列,且队列中的线程各自轮询自己的本地变量。

Java并发编程:AQS对CLH锁的优化

这个转化过程有两个要点:一是应该构建怎样的队列以及如何构建队列?为了保证公平性,我们构建的将是一个FIFO队列。构建的时候主要通过移动尾部节点tail来实现队列的排队,每个想获取锁的线程创建一个新节点并通过CAS原子操作将新节点赋给tail,然后让当前线程轮询前一节点的某个状态位。如图可以清晰看到队列结构及自旋操作,这样就成功构建了线程排队队列。二是如何释放队列?执行完线程后只需将当前线程对应的节点状态位置为解锁状态即可,由于下一节点一直在轮询,所以可获取到锁。

02

为什么自旋

下面我们提供一个简单的CLH锁实现代码,以便更好理解CLH锁的原理。其中lock与unlock两方法提供加锁和解锁操作,每次加锁解锁必须将一个CLHNode对象作为参数传入。lock方法的for循环是通过CAS操作将新节点插入队列,而while循环则是检测前驱节点的锁状态位。一旦前驱节点锁状态位允许则结束检测,让线程往下执行。解锁操作先判断当前节点是否为尾节点,如是则直接将尾节点设置为空,此时说明仅仅只有一条线程在执行,否则将当前节点的锁状态位设置为解锁状态。

Java并发编程:AQS对CLH锁的优化

03

AQS对CLH锁的优化

在CLH锁核心思想的影响下,AQS框架以CLH锁为基础。 但同时考虑到为了让CLH锁更容易实现取消与超时功能,于是对CLH锁已经做了一些改造。 主要从两方面进行了改造: 节点的结构与节点等待机制。

在结构上引入了头结点和尾节点,它们分别指向队列的头和尾,尝试获取锁、入队列、释放锁等实现都与头尾节点相关,并且每个节点都引入前驱节点和后继节点的引用;在等待机制上由原来的自旋改为阻塞唤醒。如下图,通过前驱后继节点的引用一个个节点连接起来形成一个链表队列,对头尾节点的更新必须是原子的。下面详细看看入队、检测挂起、释放出队、超时、取消等操作。

Java并发编程:AQS对CLH锁的优化

04

 入队操作

入队操作的逻辑其实是用一个无限循环进行CAS操作,即用自旋方式竞争直到成功。 将尾节点tail的旧值赋予新节点node的前驱节点,并尝试CAS操作将新节点node赋予尾节点tail,原先的尾节点的后继节点指向新建节点node。 完成上面步骤就建立起一条链表队列。 简化代码如下。

Java并发编程:AQS对CLH锁的优化

05

 阻塞操作

上面我们说到节点等待机制已经被AQS作者由自旋机制改造成阻塞机制。 一个新建的节点完成入队操作后,如果是自旋则直接进入循环检测前驱节点是否为头结点即可。 但如果改为阻塞机制,则当前线程将先检测是否为头结点且尝试获取锁。 如果当前节点为头结点并成功获取锁则直接返回,当前线程不进入阻塞,否则将当前线程阻塞。 简化代码如下。

Java并发编程:AQS对CLH锁的优化

06

 出队操作

出队的主要工作是负责唤醒等待队列中的后继节点,让所有等待节点环环相扣,每条线程有序地往下执行。如果在共享模式下出队工作将变得异常复杂,主要考虑的是对释放时竞争优化而引入了另外一种状态PROPAGATE。多条线程并发执行出队操作时可能将头结点状态改为PROPAGATE,当下一节点被唤醒时根据此状态将继续往下唤醒而不用去执行尝试获取,以达到优化效果。此处只讨论独占模式,简化代码如下。

Java并发编程:AQS对CLH锁的优化

07

 超时机制

超时的模式需要LockSupport类的parkNanos方法支持,线程在阻塞一段时间后会自 动唤醒。 每次循环将累加消耗时间,当总消耗时间大于等于自定义的超时时间时就直接返回。 简化代码如下。

Java并发编程:AQS对CLH锁的优化

08

 取消操作

队列中等待锁的队列可能因为中断或超时而涉及到取消操作,这种情况下被取消的节点不再进行锁竞争。 此过程主要完成的工作是将取消的节点移除。 先将节点node状态设置成取消状态,再将前驱节点pred的后继节点指向node的后继节点。 这里由于涉及到竞争,必须通过CAS进行操作。 CAS操作就算失败也不必理会,因为已经改了节点的状态,在尝试获取锁操作中会循环对节点的状态判断。

Java并发编程:AQS对CLH锁的优化

- END -

Java并发编程:AQS对CLH锁的优化

本文分享自微信公众号 - 码农架构(iByteCoding)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

点赞
收藏
评论区
推荐文章
秃头王路飞 秃头王路飞
4个月前
webpack5手撸vue2脚手架
webpack5手撸vue相信工作个12年的小伙伴们在面试的时候多多少少怕被问到关于webpack方面的知识,本菜鸟最近闲来无事,就尝试了手撸了下vue2的脚手架,第一次发帖实在是没有经验,望海涵。languageJavaScript"name":"vuecliversion2","version":"1.0.0","desc
浅梦一笑 浅梦一笑
4个月前
初学 Python 需要安装哪些软件?超级实用,小白必看!
编程这个东西是真的奇妙。对于懂得的人来说,会觉得这个工具是多么的好用、有趣,而对于小白来说,就如同大山一样。其实这个都可以理解,大家都是这样过来的。那么接下来就说一下python相关的东西吧,并说一下我对编程的理解。本人也是小白一名,如有不对的地方,还请各位大神指出01名词解释:如果在编程方面接触的比较少,那么对于软件这一块,有几个名词一定要了解,比如开发环
技术小男生 技术小男生
4个月前
linux环境jdk环境变量配置
1:编辑系统配置文件vi/etc/profile2:按字母键i进入编辑模式,在最底部添加内容:JAVAHOME/opt/jdk1.8.0152CLASSPATH.:$JAVAHOME/lib/dt.jar:$JAVAHOME/lib/tools.jarPATH$JAVAHOME/bin:$PATH3:生效配置
光头强的博客 光头强的博客
4个月前
Java面向对象试题
1、请创建一个Animal动物类,要求有方法eat()方法,方法输出一条语句“吃东西”。创建一个接口A,接口里有一个抽象方法fly()。创建一个Bird类继承Animal类并实现接口A里的方法输出一条有语句“鸟儿飞翔”,重写eat()方法输出一条语句“鸟儿吃虫”。在Test类中向上转型创建b对象,调用eat方法。然后向下转型调用eat()方
刚刚好 刚刚好
4个月前
css问题
1、在IOS中图片不显示(给图片加了圆角或者img没有父级)<div<imgsrc""/</divdiv{width:20px;height:20px;borderradius:20px;overflow:h
blmius blmius
1年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
小森森 小森森
4个月前
校园表白墙微信小程序V1.0 SayLove -基于微信云开发-一键快速搭建,开箱即用
后续会继续更新,敬请期待2.0全新版本欢迎添加左边的微信一起探讨!项目地址:(https://www.aliyun.com/activity/daily/bestoffer?userCodesskuuw5n)\2.Bug修复更新日历2.情侣脸功能大家不要使用了,现在阿里云的接口已经要收费了(土豪请随意),\\和注意
晴空闲云 晴空闲云
4个月前
css中box-sizing解放盒子实际宽高计算
我们知道传统的盒子模型,如果增加内边距padding和边框border,那么会撑大整个盒子,造成盒子的宽度不好计算,在实务中特别不方便。boxsizing可以设置盒模型的方式,可以很好的设置固定宽高的盒模型。盒子宽高计算假如我们设置如下盒子:宽度和高度均为200px,那么这会这个盒子实际的宽高就都是200px。但是当我们设置这个盒子的边框和内间距的时候,那
艾木酱 艾木酱
3个月前
快速入门|使用MemFire Cloud构建React Native应用程序
MemFireCloud是一款提供云数据库,用户可以创建云数据库,并对数据库进行管理,还可以对数据库进行备份操作。它还提供后端即服务,用户可以在1分钟内新建一个应用,使用自动生成的API和SDK,访问云数据库、对象存储、用户认证与授权等功能,可专
Stella981 Stella981
1年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
helloworld_28799839 helloworld_28799839
4个月前
常用知识整理
Javascript判断对象是否为空jsObject.keys(myObject).length0经常使用的三元运算我们经常遇到处理表格列状态字段如status的时候可以用到vue