以下是AQS的acquire()方法源码:

public final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();}

一步步拆解,先看下tryAcquire()方法,这个方法是由AQS子类来做具体实现的,暂不关注,重点关注AQS的几个模版方法。

addWaiter()方法

先看addWaiter()方法,参数是一个Node对象,那么就先看下Node对象所对应的类是怎样的定义:

可以发现Node类是AQS类的一个内部类,有若干的方法和属性,这时候还无法知道这些方法和属性的作用是什么,所以继续跟进addWaiter()方法中

addWaiter()方法

先看看addWaiter()方法的源码:

private Node addWaiter(Node mode) {//构造一个Node对象,参数是当前线程对象以及mode对象,mode表示该节点的共享/排他性,值为null为排他模式,不为null则共享模式Node node = new Node(Thread.currentThread(), mode);//拿到AQS的尾节点 Node pred = tail;//如果尾节点不为空if (pred != null) {//先把新加入的节点的前驱节点设置为尾节点,新加入的节点会加入队列的尾部node.prev = pred;//通过CAS操作把新节点设置为尾节点,传入原来的尾节点pred和新节点node做判断,保证并发安全if (compareAndSetTail(pred, node)) {//把新节点设置为原来尾节点的后继节点pred.next = node;//返回新节点,这个节点里封装了当前的线程return node;}}//尾节点为null,则将新节点加入队列enq(node);return node;}

enq()方法

继续跟进enq()方法,看看它是怎么将节点加入队列的:

private Node enq(final Node node) {//这是一个死循环,不满足一定的条件就不会跳出循环for (;;) {//获取AQS尾节点Node t = tail;//如果为null,其实这是个循环判断,可能下次再做判断时,就有其他线程已经往队列中添加了节点,那么tail尾节点可能就不为空了,就走else逻辑了if (t == null) { // 必须初始化//则新建一个Node对象,通过CAS设置成头节点,这个head其实是冗余节点if (compareAndSetHead(new Node()))//把尾节点设置为headtail = head;} else {//尾节点不为空,则把尾节点设置为新节点的前驱节点node.prev = t;//做CAS操作,把新节点设置为尾节点if (compareAndSetTail(t, node)) {//CAS成功后,则把新节点设置为原来尾节点的后继节点t.next = node;//返回新节点,这个节点里封装了当前的线程return t;}}}}//这个方法关注一下,可能会豁然开朗,这就是当尾节点尾null时,需要设置头节点,来做个初始化的方法,把头节点从null设置为update对象private final boolean compareAndSetHead(Node update) {return unsafe.compareAndSwapObject(this, headOffset, null, update);}

所以enq()方法是很有意思的,它通过死循环的方式,来保证节点的正确添加,可以发现只有当新节点被设置为尾节点时,当前线程才能从enq()方法返回,然后再配合上CAS,是不是有一种很抽象的感觉,节点一个一个的被加到队列中,一个一个的接着被设置为尾节点,并发的操作,串行的感觉。

所以通过addWaiter()方法,竞争同步状态失败的线程就被成功的加入到对列的尾部了

acquireQueued()方法

再接着看看acquireQueued()方法:

 final boolean acquireQueued(final Node node, int arg) {boolean failed = true;try {boolean interrupted = false;for (;;) {//获取node前继节点final Node p = node.predecessor();//如果node的前继节点是头节点,同时当前线程获取同步状态成功if (p == head && tryAcquire(arg)) {//那么把当前节点设置为头节点,同时把当前节点的前继节点置为nullsetHead(node);//再把前头节点p的后继节点设置为null,这样前头节点就没有任何引用了,帮助GC,清理前头节点p.next = null; // help GC//这里设置把标志位failed设为false,说明成功走到了这步逻辑failed = false;//要注意,无限循环只有这个出口,返回interrupted后,跳出循环,这个返回值就表示了要不要中断return interrupted;}//当node的前继节点不是头节点或者获取锁失败时,判断是否需要阻塞等待,如果需要等待,那么就调用parkAndCheckInterrupt()方法阻塞等待if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())//如果线程被中断的,那么重新设置中断状态为true,然后返回表示需要中断interrupted = true;}} finally {//如果出现不正常情况,failed标志位还没被置为false,就会取消if (failed)cancelAcquire(node);}}

shouldParkAfterFailedAcquire()方法

深入进去看看shouldParkAfterFailedAcquire()方法:

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { //获取前继节点状态int ws = pred.waitStatus;//如果等于SIGNAL,则直接返回true,表示要阻塞if (ws == Node.SIGNAL)//要注意只有这个分支会返回true return true;//如果状态大于0,表示前继节点需要做的请求被取消了,   if (ws > 0) {//这个分支循环做一件事,把所有的被取消的前继节点移除,直到waitStatus值不再大于0,然后把这个没有被取消的节点和node节点连接起来do {node.prev = pred = pred.prev;//上述代码转换成如下模式可能会更好理解,其实就是反复取值赋值(node.prev = pred;pred = pred.prev;)} while (pred.waitStatus > 0);pred.next = node;} else {//通过CAS设置前置节点的状态为SIGNALcompareAndSetWaitStatus(pred, ws, Node.SIGNAL);}//返回falsereturn false;}

这里需要再回顾acquireQueued()方法,shouldParkAfterFailedAcquire()方法的结果一旦返回false,那么acquireQueued()方法的死循环就不会跳出,还是会继续检查node的前继节点是否是头节点,同时当前线程获取同步状态是否成功,而如果shouldParkAfterFailedAcquire()方法的结果是true,就会调用parkAndCheckInterrupt()方法:

private final boolean parkAndCheckInterrupt() {//LockSupport.park()实现阻塞等待,等着unpark和interrupt叫醒他LockSupport.park(this);//检查是否被中断,清除中断状态,并返回中断标志return Thread.interrupted();}

到这步可以发现,acquireQueued()方法的死循环逻辑配合上shouldParkAfterFailedAcquire()方法的去除取消节点和设置SIGNAL状态的操作,整个队列慢慢的会趋向于:

  • 只要不是头节点,那么其他的节点都是返回true,表示需要中断,这都是shouldParkAfterFailedAcquire()方法的功劳
  • 只要不是尾节点,那么其他的节点状态都是SIGNAL,因为只有节点状态是SIGNAL,才会返回true,这也是shouldParkAfterFailedAcquire()方法的功劳

最后如果acquireQueued()方法中出现了异常,则会调用cancelAcquire()方法来取消节点。

selfInterrupt()方法

最后再看看selfInterrupt()方法:

static void selfInterrupt() {Thread.currentThread().interrupt();}

这个线程没能获取到同步状态,同时acquireQueued()方法返回true,那么就会调用selfInterrupt()方法来设置当前线程的中断状态。

acquire()方法流程图

总结

看完源码分析,做到大致熟悉,然后以代码为切入口理解流程图即可。

AQS的核心方法-acquire()解析相关推荐

  1. Java Executor源码解析(3)—ThreadPoolExecutor线程池execute核心方法源码【一万字】

    基于JDK1.8详细介绍了ThreadPoolExecutor线程池的execute方法源码! 上一篇文章中,我们介绍了:Java Executor源码解析(2)-ThreadPoolExecutor ...

  2. 【面试资料】 Java中高级核心面试知识解析

    [面试资料] Java中高级核心面试知识解析 一.Java (一). 基础 (二). 容器 (三). 并发 (四). JVM 二.网络 (一). 计算机网络知识 (二). HTTPS中的TLS 三.L ...

  3. Spring Cloud Gateway-ServerWebExchange核心方法与请求或者响应内容的修改

    Spring Cloud Gateway-ServerWebExchange核心方法与请求或者响应内容的修改 前提 本文编写的时候使用的Spring Cloud Gateway版本为当时最新的版本Gr ...

  4. 深入OKHttp源码分析(二)----OkHttp任务调度核心类Dispatcher解析

    OkHttp任务调度核心类Dispatcher解析 上一篇我们分析了okhttp的同步和异步请求的执行流程并进行了源码分析,深入OKHttp源码分析(一)----同步和异步请求流程和源码分析 那么今天 ...

  5. Java中高级核心知识全面解析——什么是Spring Cloud、需要掌握哪些知识点?(下)

    目录 一.必不可少的 Hystrix 1.什么是 Hystrix之熔断和降级 2.什么是Hystrix之其他 二.微服务网关--Zuul 1.Zuul 的路由功能 1)简单配置 2)统一前缀 3)路由 ...

  6. Android Framework 电源子系统(05)核心方法updatePowerStateLocked分析-3 更新屏保  发送通知  更新wakelock

    该系列文章总纲链接:专题分纲目录 Android Framework 电源子系统 本章关键点总结 & 说明: 本章节主要关注➕ updatePowerStateLocked 方法中 更新屏保 ...

  7. DophinScheduler ui部分 核心代码详细解析——重中之重的src文件夹里究竟有何种玄机

    2021SC@SDUSC 文章目录 一.整体结构 二.具体细节 1.components 2.images 3.js 1.dag-canvas 2.contextMenu 3.nodeStatus.j ...

  8. DophinScheduler server部分 核心代码详细解析——掌控任务和进程的呼吸与脉搏:log、monitor与registry

    2021SC@SDUSC 文章目录 一.整体结构 二.具体分析 1.log 1.LoggerRequestProcessor 2.LoggerServer 3.MasterLogFilter 2.mo ...

  9. DophinScheduler server部分 核心代码详细解析——统领全局调度全场的服务器server部分究竟干了什么?

    2021SC@SDUSC 文章目录 一.整体结构 二.细节分析 1.builder 2.entity 1.DataxTaskExecutionContext 2.DependenceTaskExecu ...

最新文章

  1. [LeetCode] Factorial Trailing Zeroes
  2. python 同步 事件 event 简介
  3. ReentrantLock可以是公平锁,sync只能是非公平锁。
  4. 扶凯:海量视频和用户时代的CDN
  5. 《spring-boot学习》-10-RabbitMQ
  6. python3 txt 读写_Python3 读写txt文件
  7. android gson解析json数据,Android中使用Gson解析JSON数据的两种方法
  8. linux 无盘 重新生成内核,Linux无盘系统_尐惢
  9. IE浏览器连不上网电脑无法联网
  10. mac下应该是一样的,ln命令
  11. MySQL InnoDB聚簇索引和普通索引浅析
  12. vscode中用快捷键 Alt + Shift + F 格式化代码不生效的问题
  13. [Revit教程]斑马:分享一个用Revit自适应构件做安全疏散距离分析的方法#S007
  14. lumia 525 android 7.1,给大神跪了!诺基亚Lumia 520成功刷上安卓7.1
  15. Json-server简单实现mock数据
  16. ICESat2数据hd5文件的预处理
  17. 上高职业技术学校计算机专科,上高职业技术学校
  18. 【C#学习】单个问号的作用
  19. Java实现输出1-100之间的完全数,输出100以内的偶数和这两个程序
  20. 把周杰伦的脸放进漫画——MangaGAN人脸照片生成漫画论文解析

热门文章

  1. 高德地图-删除多个点标记
  2. L (12). 珍珠项链
  3. 线路测量通用公式的推导及编程
  4. 判断两个事件是不是独立事件
  5. 链家房屋数据分析实战
  6. Word处理控件Aspose.Words功能演示:使用 C# 在 Word 文档中创建和修改 VBA 宏
  7. 易语言之WebshellBrute神器打造
  8. C语言:输出1~100中3的倍数,每个数之间用制表符\t隔开,并且每到4的倍数就换行
  9. nodejs 监控微信公众号关注事件推送
  10. 一分钟快速重启资源管理器