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

01

CHL锁

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

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

02

为什么自旋

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

03

AQS对CLH锁的优化

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

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

04

入队操作

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

05

阻塞操作

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

06

出队操作

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

07

超时机制

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

07

取消操作

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

- END -

aqs clh java_Java并发编程:AQS对CLH锁的优化相关推荐

  1. JUC里面的相关分类|| java并发编程中,关于锁的实现方式有两种synchronized ,Lock || Lock——ReentrantLock||AQS(抽象队列同步器)

    JUC分类 java并发编程中,关于锁的实现方式有两种synchronized ,Lock AQS--AbstractQueuedSynchronizer

  2. AQS理解之五—并发编程中AQS的理解

    AQS理解之五-并发编程中AQS的理解 首先看下uml类图: AbstractOwnableSynchronizer 这个类定义是提供一个创建锁的基础,设置一个排它线程,帮助控制和监控访问. 先看下A ...

  3. java的尝试性问题_Java并发编程实战 03互斥锁 解决原子性问题

    文章系列 摘要 在上一篇文章02Java如何解决可见性和有序性问题当中,我们解决了可见性和有序性的问题,那么还有一个原子性问题咱们还没解决.在第一篇文章01并发编程的Bug源头当中,讲到了把一个或者多 ...

  4. 学习笔记(28):Python网络编程并发编程-死锁与递归锁

    立即学习:https://edu.csdn.net/course/play/24458/296445?utm_source=blogtoedu 1.死锁(Lock()的局限性) 知识点:Lock()只 ...

  5. 徐无忌并发编程笔记:无锁机制CAS及其底层实现原理?

    徐无忌并发编程笔记:无锁机制CAS及其底层实现原理? 完成:第一遍 1.什么是CAS算法? compare and swap:比较与交换,是一种有名的无锁算法 CAS带来了一种无锁解决线程同步,冲突问 ...

  6. Java并发编程实战之互斥锁

    文章目录 Java并发编程实战之互斥锁 如何解决原子性问题? 锁模型 Java synchronized 关键字 Java synchronized 关键字 只能解决原子性问题? 如何正确使用Java ...

  7. Java并发编程—AQS原理分析

    目录 一.AQS原理简述 二.自定义独占锁及共享锁 三.锁的可重入性 四.锁的公平性 五.惊群效应 AQS全称AbstractQueuedSynchronizer,它是实现 JCU包中几乎所有的锁.多 ...

  8. 拍卖源码java_Java并发的AQS原理详解

    原文:https://juejin.im/post/5c11d6376fb9a049e82b6253 每一个 Java 的高级程序员在体验过多线程程序开发之后,都需要问自己一个问题,Java 内置的锁 ...

  9. Java并发编程AQS详解

    本文内容及图片代码参考视频:https://www.bilibili.com/video/BV12K411G7Fg/?spm_id_from=333.788.recommend_more_video. ...

最新文章

  1. python常用知识点总结-Python常用知识点汇总
  2. Android开发中Handler的经典总结
  3. 我用过的,我正在用的,比较高效、好用的命令
  4. 【转】Android子线程真的不能更新UI么
  5. oracle定时任务的编写及查看删除
  6. python散点矩阵图_用python-pandas作图矩阵
  7. gtk+学习笔记(五)
  8. 数据结构之线性结构之堆栈
  9. CFS调度器的思想的新理解
  10. 颠覆传统!麦肯锡的数字化咨询:只用5步,打造创新的数据架构
  11. kafka内部消费偏移
  12. [osx] android studio下修改avd的hosts文件
  13. 关系型数据库一致性的理解
  14. beta 发布的相关评论
  15. 数据安全治理白皮书_天融信联合发布《自动驾驶数据安全白皮书》
  16. Tkinter模拟发送邮箱验证码并在指定时间后验证码过期
  17. 程序员干货学习资源(持续更新)
  18. 开学作业——如何做好课堂笔记
  19. 把视频中人的台词去掉且保留背景音乐的方法
  20. 国内 WhatsApp 能用吗?WhatsApp对外贸企业的重要性?

热门文章

  1. STM32单片机使用ADC功能驱动手指检测心跳模块
  2. HTML5 Canvas核心技术迷你书
  3. centos7解决ping 网址报错name or service not known
  4. R语言逻辑回归logistic regression对用户收入进行预测
  5. 基于不同IP地址下的虚拟主机配置以及基于不同端口下的虚拟主机配置
  6. 冰刃·笔记 | 勒索病毒的10%和90%等式
  7. Kubernetes 安全专家(CKS)必过心得
  8. 寒冬中,这些行业正在爆发
  9. 第五届 蓝桥杯 海盗分金币 C语言
  10. 在滴滴云 DC2 云服务器上部署 Ghost