该方法是一个模板方法,用于线程获取【锁】

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;

只有CANCELLEDws > 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方法相关推荐

  1. 【java并发】AQS中acquire方法解析

    AQS,全名AbstractQueuedSynchronizer(抽象队列同步器),它是CLH(不明白的可以先了解一下CLH)的变种.它与CLH不同之处在于:        CLH是一种公平锁,它是通 ...

  2. 2021-12-01 WPF上位机 103-西门子S7协议之V区,DB区读数据方法流程解析

    文章目录 前言 一.西门子S7协议之V区,DB区读数据方法流程解析 二.使用步骤 1.读取数据 总结 前言 随着人工智能的不断发展,物联网这门技术也越来越重要,很多人都开启了物联网学习,本文就介绍了物 ...

  3. 2022-02-17 WPF上位机 120-三菱PLC协议之读写方法流程解析

    文章目录 前言 一.三菱PLC协议之读写方法流程解析 二.使用步骤 前言 随着人工智能的不断发展,物联网这门技术也越来越重要,很多人都开启了物联网学习,本文就介绍了物联网的三菱PLC协议. 提示:以下 ...

  4. 2021-12-20 WPF上位机 120-三菱PLC协议之读写方法流程解析

    文章目录 前言 一.三菱PLC协议之读写方法流程解析 二.使用步骤 前言 随着人工智能的不断发展,物联网这门技术也越来越重要,很多人都开启了物联网学习,本文就介绍了物联网的三菱PLC协议. 提示:以下 ...

  5. 2021-11-27 WPF上位机 102-西门子S7协议之I区读数据方法流程解析

    文章目录 前言 一.西门子S7协议之I区读数据方法流程解析 二.使用步骤 1.modbus读取数据代码 2.读取数据 总结 前言 随着人工智能的不断发展,物联网这门技术也越来越重要,很多人都开启了物联 ...

  6. ReentrantLock acquire方法源码解析

    public final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCL ...

  7. 刨根问底-AQS源码解析

    刨根问底-AQS源码解析 CLH同步队列 入队列 出队列 同步状态的获取与释放 独占式同步状态获取 独占式获取响应中断 独占式超时获取 独占式同步状态释放 共享式同步状态获取 共享式同步状态释放 阻塞 ...

  8. HBase - 数据写入流程解析

    本文由  网易云 发布. 作者:范欣欣 本篇文章仅限内部分享,如需转载,请联系网易获取授权. 众所周知,HBase默认适用于写多读少的应用,正是依赖于它相当出色的写入性能:一个100台RS的集群可以轻 ...

  9. 基于神策用户画像,在线教育企业线索标签体系搭建及培育全流程解析

    作者介绍:TigerHu,环球网校大数据营销产品 leader,主导数据产品线和营销 CRM 产品线. 本文内容均从作者真实实践过程出发,结合作者公司与神策数据合作真实场景,从神策用户画像产品出发,全 ...

最新文章

  1. 2017 人工智能+内容生产研究报告
  2. yii2.0 下拉菜单
  3. extjs 重设autoHeight
  4. 【Spark Summit EU 2016】在在线学习中使用Structured Streaming流数据处理引擎
  5. python精要(82)-wxpython(3)-静态文本控件与状态栏
  6. [BUUCTF-pwn]——jarvisoj_level2
  7. Boost::filesystem的使用笔记
  8. Python twisted框架使用解析
  9. 输入身份证号,输出出生日期
  10. python实例解析_python中super的用法实例解析
  11. git fatal: index file smaller than expected
  12. 机器视觉的典型应用及领域分析
  13. 五分钟学会工业机器人DH参数标定
  14. vue项目总使用高德API的轨迹展示巡航
  15. 极客时间限时免费开放全部课程!别纠结了选这几门!
  16. Linux文件系统--文件类型
  17. nginx代理图片和视频
  18. 利用NSLOOKUP命令进行DNS层次查询
  19. Spring IOC(控制反转)的理解
  20. g80 architecture overview

热门文章

  1. 《你一定爱读的极简欧洲史》读书笔记
  2. 【最新】2020年注册测绘师资格考试《测绘管理与法律法规》真题试卷及答案解析
  3. 【2018.10.12】冥土追魂
  4. python读取idx_通过Python从.idx3-ubyte文件或GZIP中提取图像 - python
  5. 区别黑白照片和彩色照片
  6. python计算标准差为什么分母要-1_为什么样本方差的分母是除以n-1
  7. ​1970年代-大规模集成芯片(LSI)、ASIC和电子游戏吃豆人的诞生
  8. 坐标系X/Y/Z/M中的M是什么意思
  9. U盘防毒最强方案(创建删不掉的autorun.inf文件夹)(删除)
  10. [BZOJ3811][UOJ#36][清华集训2014]玛里苟斯(期望 + 线性基)