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

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. php路由器怎么登录认证,PHP用户身份验证,如路由器登录
  2. 计算机硬件维修是哪个专业,计算机硬件维护须知
  3. m1mac安装linux,M1 Mac 能安装 Ubuntu 和 Linux 了 ??
  4. mastercam2019安装教程
  5. 计算机视觉会议与专家(重排版)
  6. Account group 0170 reserved for consumers
  7. k8s部署tomcat及web应用_k8s部署tomcat的yaml文件
  8. 开源纯C#轻量级数据库引擎:SharpHSQL 1.0.3.0版本
  9. Day7 子类调用父类的方法supper 绑定方法与非绑定方法
  10. oracle时间戳效率问题,时间戳问题 - Oracle开发 - ITPUB论坛-中国专业的IT技术社区...
  11. hacker代码_如何仅用7行R代码构建Hacker News Frontpage抓取工具
  12. 【LeetCode 69】Sqrt(x)
  13. CSDN使用富文本编辑器为所发布的文章生成右侧目录
  14. 【面试必背】 常问的15个MySQL数据库查询语句,
  15. Caused by: org.apache.ibatis.ognl.OgnlException: source is null for getProperty(null, mil_id)
  16. 华为eNSP BUG——关于OSPF Router ID选择问题
  17. 每次连接服务器都要source ~/.bashrc问题
  18. unity入门2.0
  19. 苹果官方付费升级内存_趁双十一大促销,赶紧升级苹果一体机升级SSD固态和液态内存吧...
  20. 美国大学计算机工程专业排名,2018美国大学计算机工程专业排名_美国大学计算机工程排名...

热门文章

  1. 如何选择合适的关键词
  2. validated 验证数组_@Validated和@Valid的区别?校验级联属性(内部类)
  3. android:text和tools:text
  4. 计算机毕业设计_基于JavaWeb servlet的教师信息管理系统(源码+mysql+文档)
  5. STM32电子万年历制作详解(RTC实战)
  6. MacBookPro M1安装 Ubuntu
  7. html如何让图片自动消失,如何让按钮背景可以渐渐显示和渐渐消失_html/css_WEB-ITnose...
  8. mac下使用夜神模拟器调试
  9. VisionMobile:“只为粉丝”或者小米并非你所想的那样
  10. 《Python编程无师自通》第11章 练习