AQS核心流程解析-acquire方法
该方法是一个模板方法,用于线程获取【锁】
public final void acquire(int arg) {if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();
}
流程解析:
1.先执行tryAcquire,这是方法是给子类实现的,用于直接获取共享变量(对state变量CAS操作),也就是【锁】
2.当tryAcquire执行失败,则执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
3.先看addWaiter(Node.EXCLUSIVE)
,Node.EXCLUSIVE
表示创建一个排他线程节点,并将节点加入到等到队列中,需要注意哦,加入队列之后并没有将线程阻塞,这步工作由acquireQueued
完成:
(1).队列为空时:
compareAndSetHead(new Node())
,就是将head和tail都指向new Node()
,即当前线程节点
(2).队列不为空时:
4.addWaiter执行完成,返回的是当前线程节点的引用,并且作为acquireQueued的参数,如此进入acquireQueued方法,再次试图获取【锁】,失败则阻塞当前线程,先看下代码:
final boolean acquireQueued(final Node node, long arg) {boolean failed = true;try {boolean interrupted = false;for (;;) {final Node p = node.predecessor();if (p == head && tryAcquire(arg)) {setHead(node);p.next = null; // help GCfailed = false;return interrupted;}if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())interrupted = true;}} finally {if (failed)cancelAcquire(node);}}
参数node,就是addWaiter的返回值,是已经加入到队列的当前线程的节点,这里需要有几点需要提一下:
(1). 当前节点一定在队尾;
(2). failed变量只有内部流程抛异常的时候,才会保持true,导致finally执行到cancelAcquire
,简单讲就是当前线程获取锁的过程发生异常会放弃锁。
在acquire
一开始的时候就试图获取锁,失败了才会执行到这个地方来,这个过程中线程的运行环境瞬息万变,那么如果当前线程加入队列之后,现在已经只剩他自己了,那不应该阻塞,应该再试试看能不能获取到锁,所以看到代码if (p == head && tryAcquire(arg)) {
的判断,如果很幸运成功,那么需要将当前节点从队列中"弹"出去,然后再退出,"弹出去"的过程其实就是将当前节点变成head节点(一个空节点):
如果还是没有成功,原因有两个:
(1). 当前节点不是首节点(前面还有人等着呢);
(2). 争夺【锁】失败
此时执行if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
,字面义就是先判断线程应不应该被阻塞,如果不应该,则进入下一次for(;;)
自旋,如果应该阻塞,则调用parkAndCheckInterrupt
,阻塞失败的话,也会进入下一次自旋。
5.那怎么知道应不应该阻塞呢?看看shouldParkAfterFailedAcquire
的逻辑:
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {int ws = pred.waitStatus;if (ws == Node.SIGNAL)return true;if (ws > 0) {do {node.prev = pred = pred.prev;} while (pred.waitStatus > 0);pred.next = node;} else {compareAndSetWaitStatus(pred, ws, Node.SIGNAL);}return false;}
这个方法中最重要的就是搞懂waitStatus
变量的作用,这个值总共有以下几种值:
static final int CANCELLED = 1;
static final int SIGNAL = -1;
static final int CONDITION = -2;
static final int PROPAGATE = -3;
只有CANCELLED
是ws > 0
的,它表示某个线程节点取消等待,放弃参与锁竞争,如果发现前置节点是这种状态,那么就要跳过他,再往链表的前面界面找找有没有"正常"的节点,这也是if (ws > 0) {
分支的执行目的,这个过程动图表示:
ws == Node.SIGNAL
直接返回true,关键在于理解SIGNAL
的含义,我认为有两个含义:
(1). 节点本身还没有获取到锁;
(2). 存在后继节点,等待唤醒
if (ws == Node.SIGNAL)
这个ws是前一个节点的状态,他是SIGNAL
说明他也在等待锁,那么当前我这个节点还想个屁吃,直接阻塞着等待就好了。
else
包含了pred.waitStatus == 0 和 PROPAGATE
状态,注意哦,我们现在是刚刚结束addWaiter的流程,走到这里的,所以是尾节点,当然也有可能队列中只有一个节点,所以也有可能既是头节点又是尾节点,那么前置节点的状态有几种情况:
(1). 头节点,则pred.waitStatus == 0
(2). 原来的尾节点;如果前直节点是EXCLUSIVE则pred.waitStatus == 0
,如果前节点是SHARED,则有可能是pred.waitStatus ==PROPAGATE
,这种情况发生在链路上写线程释放锁的时候,会将后续的读线程状态置为PROPAGATE
线程环境瞬息万变,这两种情况,都不急着阻塞,因为是有可能马上就没有线程竞争了,尤其是第一种情况,前置节点是第一个,但是加锁失败了,等一等可能就到我们自己了,所以先用compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
标记一下,然后回到上一层的acquireQueued
方法会发现其实是进入到下一次自旋中。
如果说shouldParkAfterFailedAcquire
返回true了,那么没办法了,说明穷尽伎俩也没不可能马上拿到锁了,那就等着呗,就会进入parkAndCheckInterrupt
中:
private final boolean parkAndCheckInterrupt() {LockSupport.park(this);return Thread.interrupted();}
这简单的代码也是AQS的要素之一,就是LockSupport.park方法啦,把线程阻塞,需要注意的是,阻塞期间如果被interrupt中断,是不抛出异常,而是返回中断标识,if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())
的分支在发生中断的时候会进入分支代码,interrupted = true;
标记下曾经发生过中断,这么做是因为调用过Thread.interrupted()
之后,线程的中断标志就会被重置了,如果以后想要知道线程在等待、自旋过程中有没有发生过中断,就没办法了,所以设置了一个interrupted
变量来保存历史。
acquireQueued
会一直等到线程获取锁才返回,这个过程中存在多次自旋、线程阻塞,而他的返回值就是interrupted
变量,如果返回true表示曾经发生过中断,那么就在线程上"还原现场",利用selfInterrupt();
设置一下中断标志,这样外部调用方才能感知到线程曾经发生过中断。
AQS核心流程解析-acquire方法相关推荐
- 【java并发】AQS中acquire方法解析
AQS,全名AbstractQueuedSynchronizer(抽象队列同步器),它是CLH(不明白的可以先了解一下CLH)的变种.它与CLH不同之处在于: CLH是一种公平锁,它是通 ...
- 2021-12-01 WPF上位机 103-西门子S7协议之V区,DB区读数据方法流程解析
文章目录 前言 一.西门子S7协议之V区,DB区读数据方法流程解析 二.使用步骤 1.读取数据 总结 前言 随着人工智能的不断发展,物联网这门技术也越来越重要,很多人都开启了物联网学习,本文就介绍了物 ...
- 2022-02-17 WPF上位机 120-三菱PLC协议之读写方法流程解析
文章目录 前言 一.三菱PLC协议之读写方法流程解析 二.使用步骤 前言 随着人工智能的不断发展,物联网这门技术也越来越重要,很多人都开启了物联网学习,本文就介绍了物联网的三菱PLC协议. 提示:以下 ...
- 2021-12-20 WPF上位机 120-三菱PLC协议之读写方法流程解析
文章目录 前言 一.三菱PLC协议之读写方法流程解析 二.使用步骤 前言 随着人工智能的不断发展,物联网这门技术也越来越重要,很多人都开启了物联网学习,本文就介绍了物联网的三菱PLC协议. 提示:以下 ...
- 2021-11-27 WPF上位机 102-西门子S7协议之I区读数据方法流程解析
文章目录 前言 一.西门子S7协议之I区读数据方法流程解析 二.使用步骤 1.modbus读取数据代码 2.读取数据 总结 前言 随着人工智能的不断发展,物联网这门技术也越来越重要,很多人都开启了物联 ...
- ReentrantLock acquire方法源码解析
public final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCL ...
- 刨根问底-AQS源码解析
刨根问底-AQS源码解析 CLH同步队列 入队列 出队列 同步状态的获取与释放 独占式同步状态获取 独占式获取响应中断 独占式超时获取 独占式同步状态释放 共享式同步状态获取 共享式同步状态释放 阻塞 ...
- HBase - 数据写入流程解析
本文由 网易云 发布. 作者:范欣欣 本篇文章仅限内部分享,如需转载,请联系网易获取授权. 众所周知,HBase默认适用于写多读少的应用,正是依赖于它相当出色的写入性能:一个100台RS的集群可以轻 ...
- 基于神策用户画像,在线教育企业线索标签体系搭建及培育全流程解析
作者介绍:TigerHu,环球网校大数据营销产品 leader,主导数据产品线和营销 CRM 产品线. 本文内容均从作者真实实践过程出发,结合作者公司与神策数据合作真实场景,从神策用户画像产品出发,全 ...
最新文章
- php路由器怎么登录认证,PHP用户身份验证,如路由器登录
- 计算机硬件维修是哪个专业,计算机硬件维护须知
- m1mac安装linux,M1 Mac 能安装 Ubuntu 和 Linux 了 ??
- mastercam2019安装教程
- 计算机视觉会议与专家(重排版)
- Account group 0170 reserved for consumers
- k8s部署tomcat及web应用_k8s部署tomcat的yaml文件
- 开源纯C#轻量级数据库引擎:SharpHSQL 1.0.3.0版本
- Day7 子类调用父类的方法supper 绑定方法与非绑定方法
- oracle时间戳效率问题,时间戳问题 - Oracle开发 - ITPUB论坛-中国专业的IT技术社区...
- hacker代码_如何仅用7行R代码构建Hacker News Frontpage抓取工具
- 【LeetCode 69】Sqrt(x)
- CSDN使用富文本编辑器为所发布的文章生成右侧目录
- 【面试必背】 常问的15个MySQL数据库查询语句,
- Caused by: org.apache.ibatis.ognl.OgnlException: source is null for getProperty(null, mil_id)
- 华为eNSP BUG——关于OSPF Router ID选择问题
- 每次连接服务器都要source ~/.bashrc问题
- unity入门2.0
- 苹果官方付费升级内存_趁双十一大促销,赶紧升级苹果一体机升级SSD固态和液态内存吧...
- 美国大学计算机工程专业排名,2018美国大学计算机工程专业排名_美国大学计算机工程排名...
热门文章
- 如何选择合适的关键词
- validated 验证数组_@Validated和@Valid的区别?校验级联属性(内部类)
- android:text和tools:text
- 计算机毕业设计_基于JavaWeb servlet的教师信息管理系统(源码+mysql+文档)
- STM32电子万年历制作详解(RTC实战)
- MacBookPro M1安装 Ubuntu
- html如何让图片自动消失,如何让按钮背景可以渐渐显示和渐渐消失_html/css_WEB-ITnose...
- mac下使用夜神模拟器调试
- VisionMobile:“只为粉丝”或者小米并非你所想的那样
- 《Python编程无师自通》第11章 练习