AQS中那些不得不说的理论知识
点击关注公众号,利用碎片时间学习
来源:blog.csdn.net/weixin_43823391/
article/details/114259377
一、概念
AQS全称为AbstractQueuedSynchronizer
(后面都以AQS简称),翻译过来叫做抽象队列同步器,是一个抽象类,其实也是一个单机多线程基础同步框架,这个框架通过对同步状态的原子性管理,实现对多线程的管理。子类应该定义一个非公开的内部类继承AQS,并实现其中方法。此部分介绍以独占模式为主。
子类根据需要需要重写下面的方法:
tryAcquire
tryRelease
tryReleaseShared
isHeldExclusively等方法,并确保是线程安全的。
二、AQS实现的基础
1、CAS:是compareandswap的简称,从字面上理解就是比较并更新。CAS操作是由Unsafe工具类来实现的,其操作具有原子性,我们一般通过CAS来改变状态。(状态被volatile修饰,因此具有可见性和有序性,所以CAS改变状态时是线程安全的)
2、队列:用来保存等待操作的资源,其数据结构一般为链表。当线程的请求在短时间内得不到满足时,线程会被包装成某种类型的数据结构放入队列中,当条件满足时则会拿出队列去重新获取锁。
3、状态:AQS及其子类的核心,AQS及其子类所有操作都是依据状态进进行的。状态一般会设置成volatile,保证其具有可见性,一定程度上具有有序性。它为 0 的时候代表没有线程占有锁,可以去争抢这个锁,用 CAS 将 state 设为 1,如果 CAS 成功,说明抢到了锁,这样其他线程就抢不到了。如果锁重入的话,state进行 +1 就可以,解锁就是减 1,直到 state 又变为 0,代表释放锁,所以 lock()
和unlock()
必须要配对。共享模式的话,状态也是进行+1,解锁就是减一。
4、locksupport类的park和unpark方法。AQS队列的阻塞调用了Unsafe类中的park,park方法则借助于操作系统的实现来进行阻塞的,借此实现阻塞的两个特性:
不耗 CPU 等待;
线程安全。
三、AQS术语解释
1、队列
整个框架的关键就是如何管理被阻塞的线程的队列,该队列是严格的FIFO队列,因此,框架不支持基于优先级的同步。队列根据需要分为同步队列和条件队列。它们都是通过下面要介绍的AQS内部类Node来实现的。
1.1、 同步队列(阻塞队列)
同步队列是双向链表结构,既然能做双向链表,它可以用来保存等待的线程以及线程的状态等信息。头结点是一个哨兵节点(是一个附加的链表节点,该节点作为第一个节点,但是它其实并不存储任何东西,只是为了操作的方便而引入的)。
1.2、 条件队列(等待队列)
当使用Condition的时候,等待队列的概念就出来了。Condition的获取一般都要与一个锁Lock相关,一个锁上面可以生产多个Condition。即一个锁内的代码块可以新建多个Condition对象。
1.3 同步队列和条件队列的关系
Condition接口的主要实现类是AQS的内部类ConditionObject,每个Condition对象都包含一个等待队列。该队列是Condition对象实现等待/通知的关键。AQS中同步队列与等待队列的关系如下:
2、Node
AQS的一个内部类。用来包装竞争资源的线程,并将其组装成链表结构。
2.1 prev
同步队列的前驱节点,条件队列没有这个概念。
2.2 next
同步队列的后继节点,条件队列没有这个概念。
2.3 thread
竞争资源的线程。
2.4 nextWaiter
条件队列的下一个节点,同步队列没有这个概念。
2.5 waitStatus
队列中节点的等待状态。
static final int CANCELLED = 1;
此节点的线程被取消 独占模式 共享模式static final int SIGNAL = -1;
此节点的后继节点线程被挂起,需要被唤醒 独占模式static final int CONDITION = -2;
此节点的线程在等待信号,也表明当前节点不在同步队列中,而在条件队列中static final int PROPAGATE = -3;
此节点下一个acquireShared应该无条件传播 共享模式
这四个属性就是waitStatus属性的具体状态,还有一个隐式的具体状态,即waitStatus初始化时为0。在独占模式下,我们只需要用到CANCELLED和SIGNAL,这里需要注意的是SIGNAL,它代表的不是自己线程的状态,而是它后继节点的状态,当一个节点的waitStatus被置为SIGNAL时,表明此节点的后继节点被挂起,当此节点释放锁或被取消放弃拿锁时,应该唤醒后继节点。而在共享模式时,我们会用到CANCELLED和PROPAGATE
3、ConditionObject
是AQS的内部类ConditionObject。
3.1 firstWaiter
条件队列的第一个节点。
3.2 lastWaiter
条件队列的最后一个节点。
3.2 await()
3.2.1 创建一个条件队列节点,把自己加入到条件队列中,必要的时候初始化条件队列;
3.2.2 因为调用await的线程都持有锁,所以接下来需要执行AQS的release方法释放当前线程持有的锁,即让出锁,让其他线程执行;
3.2.3 利用park方法将当前线程挂起,等待唤醒;
3.2.4 其他线程调用signal()
方法唤醒该线程(该方法是隐式的,在代码中没有体现,因为是多线程执行,体现在signal())
将节点从条件队列转移到等待队列
调用unpark方法唤醒线程
重新竞争锁。这个步骤也很关键,这里可以区分公平锁和非公平锁
3.3 signal()
3.3.1 将条件队列中的的节点(firstWaiter)转移到同步队列中
3.3.2 把刚转移到同步队列中的节点前驱的waitstatus改为SIGNAL(-1)
,用来唤醒后继节点。
4、队列同步器模式
4.1 共享模式
同一时间只有一个线程能拿到锁执行。
4.2 独占模式
同一时间有多个线程可以拿到锁协同工作。
4.3 公平锁
多个线程按照申请锁的顺序去获得锁,线程会直接进入队列去排队,永远都是队列的第一位才能得到锁。
4.4 非公平锁
多个线程去获取锁的时候,会直接去尝试获取,获取不到,再去进入等待队列,如果能获取到,就直接获取到锁。
4.5 可重入锁
广义上的可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁(前提得是同一个对象或者class),这样的锁就叫做可重入锁。ReentrantLock和synchronized都是可重入锁。
在ReentrantLock上的体现:
final boolean nonfairTryAcquire(int acquires) { //这里我就跳过着讲解了,ReentrantLock模式使用的是非公平锁,这样能提高系统的响应性能final Thread current = Thread.currentThread();int c = getState(); //获取资源的状态,if (c == 0) { //为0就是别人还没有获取到锁,这个时候当前线程就可以获取到锁if (compareAndSetState(0, acquires)) { //用cas的方式获取到锁setExclusiveOwnerThread(current); //这个方法里面就只有这一句 exclusiveOwnerThread = thread; 设置当前线程是独占线程return true;}}else if (current == getExclusiveOwnerThread()) { //重点来了,这个方法就是主要判断是不是可重入的,如果之前的判断资源的状态是被上锁了,就会执行到这里,如果判断是本线程int nextc = c + acquires; //把资源的的请求次数加1if (nextc < 0) // 当然也不是可以不限加的,如果超出的int的范围,抛出一个error的错误throw new Error("Maximum lock count exceeded");setState(nextc); //设置资源状态return true;}return false;
}
总结:ReentrantLock可重入主要体现在current == getExclusiveOwnerThread()
这个判断方法上面。如果是当前重入线程,资源状态添加请求数,注意释放的时候也是要释放这个多次的。
5、acquire(int arg) 线程竞争锁
竞争锁方法的路径tryAcquire()->addWaiter()->acquireQueued()->selfInterrupt()
,其中tryAcquire()需要根据不同的业务场景,由不同实现类实现。由于是多线程模式,该方法使用了很多for (;;)
来确保业务一定执行,具体可以参考源码。对应下面四个步骤:
5.1、竞争锁。竞争成功终止操作,竞争失败执行以下步骤;
5.2、将线程包装成独占式节点,并入队,放到队尾;
5.3、 当前线程加入等待队列后,会通过acquireQueued方法基于CAS自旋不断尝试获取资源,直至获取到资源竞争成功移除该节点并终止操作;
竞争失败修改前驱节点waitstatus=SIGNAL(-1)
,则会调用park阻塞线程。
5.4、 自我中断锁(用户线程第一次获取锁失败之后,进入CLH队列,此时会中断该线程)。
6、release(int arg) 线程释放锁
释放锁的路径tryRelease()->unparkSuccessor()
,其中tryAcquire()需要根据不同的业务场景,由不同实现类实现,
6.1 由于模板模式,不同的子类实现不同,一般是修改锁状态;
6.2 获取等待队列的头节点,调用unpark()
唤醒后继节点的线程。
7、为什么唤醒后继节点中,是从后向前遍历?
因为cancelAcquire方法的处理过程中只设置了next的变化,没有设置prev的变化,在最后有这样一行代码:node.next = node
。并发情况下从前向后遍历的话,可能就死循环了,所以这时只有prev是稳定的。
唤醒后继节点的线程后,被唤醒的线程在parkAndCheckInterrupt
方法,返回线程在park过程中是否被用户中断过,然后到acquireQueued
方法中,如果该节点的前驱节点是头节点,则尝试获取资源,成功获取资源后,将是否被中断标识返回acquire方法,如果被中断过,那此时中断。
如果被唤醒的线程所在节点的前继节点不是头结点,经过shouldParkAfterFailedAcquire
的调整(清除无效(waitState=CANCELLED
)的节点),也会移动到等待队列的前面,直到其前继节点为头结点。
,如果该节点的前驱节点是头节点,则尝试获取资源,成功获取资源后,将是否被中断标识返回acquire方法,如果被中断过,那此时中断。如果被唤醒的线程所在节点的前继节点不是头结点,经过shouldParkAfterFailedAcquire
的调整(清除无效(waitState=CANCELLED
)的节点),也会移动到等待队列的前面,直到其前继节点为头结点。
推荐:
主流Java进阶技术(学习资料分享)
PS:因为公众号平台更改了推送规则,如果不想错过内容,记得读完点一下“在看”,加个“星标”,这样每次新文章推送才会第一时间出现在你的订阅列表里。点“在看”支持我们吧!
AQS中那些不得不说的理论知识相关推荐
- 关于mpi的理论知识以及编写程序来实现数据积分中的梯形积分法。
几乎所有人的第一个程序是从"hello,world"程序开始学习的 #include "mpi.h" #include <stdio.h> int ...
- 《个人理财》书中的精髓:如何通过金融学理论知识的学习,建立更加完善的金融体系,让自己更加富有。
<个人理财>书中的精髓:如何通过金融学理论知识的学习,建立更加完善的金融体系,让自己更加富有. 对于很多人来说,个人理财是他们接触到的第一门也是唯一一门理财课程,对于没有系统掌握理财知识的 ...
- AQS中公平锁和非公平锁区别,你知道么
点击关注公众号,实用技术文章及时了解 来源:blog.csdn.net/weixin_43823391/ article/details/114259418 一.概念 注意:因为ReentrantLo ...
- 理解为何用期望最大化或梯度下降等启发式方法处理非凸函数在实际中如此有效,对于理论计算机科学而言是一大挑战
理解为何用期望最大化或梯度下降等启发式方法处理非凸函数在实际中如此有效,对于理论计算机科学而言是一大挑战 一本关于理论计算机科学和机器学习之间关联的高水平.快节奏的集大成之作-<机器学习算法&g ...
- 用VC进行COM编程所必须掌握的理论知识
用VC进行COM编程所必须掌握的理论知识 这篇文章是给初学者看的,尽量写得比较通俗易懂,并且尽量避免编程细节.完全是根据我自己的学习体会写的,其中若有技术上的错误之处,请大家多多指正. 一.为什么要用 ...
- oracle rac理论知识
oracle数据库高可靠性高性能的特性是很多企业需要的,这些年一直给各大政府企业做oracle咨询与规划,实施安装以及维护,回头看看,自己已经忘记大部分oracle rac的整体具体架构理论知识,现在 ...
- INLINE HOOK过驱动保护的理论知识和大概思路
INLINE HOOK过驱动保护的理论知识和大概思路,简单驱动保护就是简单的HOOK掉内核API的现象 找到被HOOK的函数的当前地址在此地址处先修改页面保护属性然后写入5个字节.5个字节就是一个简单 ...
- Web自动化测试理论知识
Web 自动化理论知识 1.自动化测试概述 概念:用工具代替/辅助人工完成完成软件测试活动的过程 特点: 可以对程序的新版本自动执行回归测试 可以执行一些手工测试困难或不可能进行的测试 ...
- 如何将计算机专业知识和水文结合,2016水文勘测理论知识及参考答案 B卷
理论知识及参考答案 Ⅰ.必答题(75分) 一.单选题(15分) (在每小题的空档中填入所选内容的序号,每题1分) 1.悬移质含沙量的大小对流速脉动有一定,含沙量增大,流速脉动将( A ),特别是高含沙 ...
最新文章
- HttpPost导包遇到的问题
- aitken插值方法的c++代码_无人驾驶路径规划技术-三次样条插值曲线及Python代码实现...
- Docker Swarm集群搭建
- Java RMI 多个JVM间相互通信
- ssm上传文件进度条_ssm学习笔记-三种文件上传方式
- bootstraptable获得所有行_2020广汽本田安全中国行·首届道路安全创新大赛成功举办...
- SDM For Face Alignment 流程介绍及Matlab代码实现之训练篇
- python定义符号常量_python从零开始学习(二):python中的变量与常量
- Flask - 基础
- python教育版_pycharm教育版下载
- 新年新气象,牛年更牛,开始新的征程
- Resolve operation not in progress, we are not resuming.
- 电脑C盘空间严重不足,教你5招!电脑内存瞬间多出10个G
- 关于参加学科竞赛的心得感想
- centos7重新调整分区大小
- TSC03数传电台介绍,便携式基站,防护等级IP67
- 应用回归分析之岭回归(Ridge Regression,RR)
- Spring源码解析三
- 仿小米官网登录功能的实现
- php相册实现图片上传源码,php 图片上传源码下载[gif,jpg]
热门文章
- OPPO、vivo 小游戏正式上线,Cocos 率先支持
- 220V和380V电器设备电流计算方法
- HP大中华区总裁孙振耀撰文谈退休并畅谈人生
- Java/Android 设计模式系列(7)--装饰者模式
- ElasticSearch 亿级数据检索深度性能优化
- android visibility动画,android – 动画和setVisibility
- 计算机基础公开课案例,大学计算机基础案例教学与教学案例的设计
- 机敏问答[常微][5] #20210622
- 在华清远见的学习感悟
- html表格边框仅一条线,css 设置table边线 为一条线