AQS是什么?AbstractQueuedSynchronizer之AQS原理及源码深度分析

文章目录

  • 一、AQS概述
    • 1、什么是AQS
    • 2、技术解释
    • 3、基本原理
    • 4、AQS为什么这么重要
  • 二、AQS数据结构
    • 1、AQS的结构
    • 2、ReentrantLock与AbstractQueuedSynchronizer
    • 3、AQS的state变量
    • 4、AQS的队列
    • 5、AQS的Node
      • (1)Node的waitStatus
      • (2)属性说明
  • 三、ReentrantLock的lock方法分析AQS源码
    • 1、类图
    • 2、FairSync和NonfairSync
    • 3、tryAcquire():尝试获取锁
    • 4、addWaiter():入队
    • 5、acquireQueued():队列线程挂起
    • 6、cancelAcquire():中断等异常导致的取消
  • 四、ReentrantLock的unlock方法分析AQS源码
    • 1、unlock
    • 2、tryRelease():释放锁
    • 3、unparkSuccessor():唤醒等待线程
  • 五、大总结
  • 参考资料

一、AQS概述

1、什么是AQS

AQS 的全称为(AbstractQueuedSynchronizer),这个类在 java.util.concurrent.locks 包下面。
在这里插入图片描述
其中,AbstractOwnableSynchronizer是AbstractQueuedLongSynchronizer和AbstractQueuedSynchronizer的父类。
在这里插入图片描述
其中AbstractOwnableSynchronizer和AbstractQueuedLongSynchronizer是jdk1.6引入的,AbstractQueuedSynchronizer是jdk1.5引入的。

2、技术解释

AQS是用来实现锁或者其他同步器组件的公共基础部分的抽象实现,是重量级基础框架及整个JUC体系的基石,主要用于解决锁分配给“谁”的问题。

AQS用于实现依赖于先进先出 (FIFO) 等待队列的阻塞锁和相关同步器(信号量、事件等)。并通过一个int类型变量表示持有锁的状态。

如图所示,AQS利用CLH(Craig、Landin and Hagersten,是一个单向链表,但AQS中的队列是CLH变体的虚拟双向队列FIFO)队列 加上一个int类型的公共变量实现的。
在这里插入图片描述

AbstractQueuedLongSynchronizer:此类具有与完全相同 AbstractQueuedSynchronizer 的结构、属性和方法,不同之处在于所有与状态相关的参数和结果都定义为 long 而不是 int。在创建同步器(如需要 64 位状态的多级锁和屏障)时,此类可能很有用。AbstractQueuedLongSynchronizer并没有具体实现类,目前还是用的AbstractQueuedSynchronizer。

3、基本原理

抢到资源的线程直接使用处理业务逻辑,抢不到资源的线程必然需要排队等候,而存储这些排队等候的资源就是AQS中的队列(CLH队列的变体-双端队列)。

如果共享资源被占用,就需要一定的阻塞等待唤醒机制来保证锁分配,这个机制主要是CLH队列的变体来实现的,将暂时获取不到锁的线程加入到队列中,这个队列就是AQS同步队列的抽象实现。它将要请求共享资源的线程及自身的等待状态封装成队列的结点对象(Node),通过CAS、自选以及LockSupport.park()的方式,维护state变量的状态,使并发达到同步的效果。

4、AQS为什么这么重要

下图是AQS的众多实现类,我们发现,JUC中的许多非常重要的类,都有着AQS的影子:
在这里插入图片描述
我们平常用的ReentrantLock、Semaphore、CountDownLatch等等,这都是定义了程序员和锁交互的使用层API,隐藏了实现细节,使用就可以。而AQS就是统一规范并简化了锁的实现,将其抽象出来,屏蔽了同步状态管理、同步队列的管理和维护、阻塞线程排队和通知、唤醒机制等等。所以,AQS是一切锁和同步组件实现的公共基础部分

二、AQS数据结构

1、AQS的结构

AQS使用一个volatile的int类型的成员变量来表示同步状态,通过内置FIFO队列来完成资源获取的排队工作将每条要去抢占资源的线程封装成一个Node结点来实现锁的分配,通过CAS完成对state值的修改。
在这里插入图片描述
其中Node中也包含头指针和尾指针,也包含了一个重要的属性waitStatus,这里区别于AQS的state。
在这里插入图片描述

2、ReentrantLock与AbstractQueuedSynchronizer

在这里插入图片描述

3、AQS的state变量

该变量位0时意味着没有线程占用,大于等于1意味着有线程占用,其它线程需要等候。
在这里插入图片描述

4、AQS的队列

CLH:Craig、Landin and Hagersten,是一个单向链表,但AQS中的队列是CLH变体的虚拟双向队列FIFO。
其数据结构图如下:
在这里插入图片描述
官方图是单向链表,但是AQS实质是双端队列。
在这里插入图片描述

5、AQS的Node

Node分共享和独占模式

(1)Node的waitStatus

Node就是等候区的线程的存储单元,一个线程对应一个Node。

Node的等待状态waitStatus是一个int类型的变量(区别于AQS的stete)。总共有如下几种:

// 线程被取消了
static final int CANCELLED =  1;
// 后继线程需要唤醒
static final int SIGNAL    = -1;
// 等待condition唤醒
static final int CONDITION = -2;
// 共享或同步状态获取将会无条件地传播下去
static final int PROPAGATE = -3;// 初始为0,状态是上面几种
volatile int waitStatus;// 前驱结点
volatile Node prev;// 后继节点
volatile Node next;// 当前线程
volatile Thread thread;

(2)属性说明

在这里插入图片描述
其中waitStatus共有5个状态:
1.为0时,当第一个Node被初始化时的默认值。
2.为CANCELLED时(-1),表示线程获取锁的请求已经取消了。
3.为CONDITION(-2),表示节点在等待队列中,节点线程等待唤醒。
4.为PROPAGATE(-3),当前线程处在SHARED情况下,该字段才会使用。
5.SIGNAL(-1),表示线程已经准备好了,就等资源释放了。

三、ReentrantLock的lock方法分析AQS源码

1、类图

Lock接口的实现类,基本都是通过【聚合】了一个【队列同步器】的子类(Sync)完成线程访问控制的。
在这里插入图片描述

2、FairSync和NonfairSync

FairSync是公平锁,讲究先来先到,线程在获取锁时,如果这个锁的等待队列中已经有线程在等待,那么当前线程就会进入等待队列中;
NonfairSync是非公平锁,不管是否有等待队列,如果可以获取锁,则立刻占有锁的对象。也就是说队列的第一个排队线程苏醒后,不一定就是排头的这个线程获取锁,它还是需要参加竞争(存在线程竞争的情况下),所以有可能出现后来的线程插队获取锁的现象。

它们的源码相差很小:
在这里插入图片描述
最终调用的acquire(1),就是AQS的核心方法:

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

3、tryAcquire():尝试获取锁

tryAcquire是AQS中定义的,但是由FairSync和NonfairSync重写:
在这里插入图片描述
我们分析NonfairSync:tryAcquire方法。

protected final boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires);
}final boolean nonfairTryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState(); // 获取锁状态if (c == 0) { // 如果资源未被占用,compareAndSetState使用cas获取锁,设置锁状态if (compareAndSetState(0, acquires)) {// 设置所属线程为当前线程setExclusiveOwnerThread(current);// 获取锁return true;}}// 如果当前资源被占用,并且是当前线程(可重入)else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc); // state + 1,仍然可以获取锁(可重入)return true;}return false;
}

所以,tryAcquire()是相当于做了一次尝试,尝试获取锁,如果获取锁那就皆大欢喜,获取不到就继续addWaiter(Node.EXCLUSIVE), arg)方法。

4、addWaiter():入队

// java.util.concurrent.locks.AbstractQueuedSynchronizer#addWaiter
// mode – Node.EXCLUSIVE 表示独占,Node.SHARED 表示共享
private Node addWaiter(Node mode) {// 一个Node包含一个当前线程的实例Node node = new Node(Thread.currentThread(), mode);// Try the fast path of enq; backup to full enq on failureNode pred = tail; // 尾指针 ,初始为nullif (pred != null) { // 不为null时node.prev = pred;if (compareAndSetTail(pred, node)) { // 设置尾指针为当前pred.next = node;return node;}}// 入队enq(node);return node;
}// 将节点插入队列,必要时进行初始化
private Node enq(final Node node) {for (;;) { // 死循环不断重试Node t = tail; // 尾结点if (t == null) { // Must initialize 虚拟头结点必须初始化,里面没有线程if (compareAndSetHead(new Node()))tail = head;} else {node.prev = t;if (compareAndSetTail(t, node)) { // 设置尾结点为当前节点t.next = node; // 所有的Node形成一条链表return t;}}}
}

最终形成如下的数据结构:
在这里插入图片描述

5、acquireQueued():队列线程挂起

// arg = 1
// Node是当前节点
final boolean acquireQueued(final Node node, int arg) {boolean failed = true;try {boolean interrupted = false;for (;;) {// predecessor获取前置节点final Node p = node.predecessor();// 如果是头结点,直接tryAcquire尝试获取锁if (p == head && tryAcquire(arg)) {// 获取锁成功,将node设为头结点setHead(node);// 将头结点的关联取消p.next = null; // help GCfailed = false;return interrupted;}// 尝试获取锁失败 或者不是第一个节点// shouldParkAfterFailedAcquire : 设置前置节点的waitStatue0->-1,首次false,再次为true,再来个线程直接trueif (shouldParkAfterFailedAcquire(p, node) &&// LockSupport.park暂停当前线程parkAndCheckInterrupt())interrupted = true;}} finally {if (failed)cancelAcquire(node);}
}

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {// 获取前驱结点的状态int ws = pred.waitStatus; // 0if (ws == Node.SIGNAL) // Node.SIGNAL :-1 即等待被占用的资源释放,直接返回true/** This node has already set status asking a release* to signal it, so it can safely park.*/return true;if (ws > 0) { // > 0是CANCELLED状态,忽略改状态的节点,重新链接队列/** Predecessor was cancelled. Skip over predecessors and* indicate retry.*/do {node.prev = pred = pred.prev;} while (pred.waitStatus > 0);pred.next = node;} else { // 将当前节点的前驱结点设置为SIGNAL : -1,用于后续唤醒操作/** waitStatus must be 0 or PROPAGATE.  Indicate that we* need a signal, but don't park yet.  Caller will need to* retry to make sure it cannot acquire before parking.*/compareAndSetWaitStatus(pred, ws, Node.SIGNAL); // 把前置节点的waitStatue 改为 -1}return false;
}
// 暂停
private final boolean parkAndCheckInterrupt() {LockSupport.park(this); // 线程挂起,等待被unpark、被中断return Thread.interrupted(); // 返回当前线程中断状态,并清空中断状态
}

6、cancelAcquire():中断等异常导致的取消

如果在自旋中,并没有获取锁,而是异常原因中断了,就会执行finally 中的cancelAcquire方法。

private void cancelAcquire(Node node) {// Ignore if node doesn't existif (node == null)return;node.thread = null;// Skip cancelled predecessorsNode pred = node.prev; // 前一个节点while (pred.waitStatus > 0) // CANCELLED:1,再找前一个节点node.prev = pred = pred.prev;// predNext is the apparent node to unsplice. CASes below will// fail if not, in which case, we lost race vs another cancel// or signal, so no further action is necessary.Node predNext = pred.next; // 下一个节点// Can use unconditional write instead of CAS here.// After this atomic step, other Nodes can skip past us.// Before, we are free of interference from other threads.node.waitStatus = Node.CANCELLED; // 本节点的waitStatue设置为CANCELLED// If we are the tail, remove ourselves.if (node == tail && compareAndSetTail(node, pred)) { // 如果本节点为尾结点,就将尾结点设为上一个不为CANCELLED的节点compareAndSetNext(pred, predNext, null); // 前一节点的next节点设为null} else { // 如果不是队尾// If successor needs signal, try to set pred's next-link// so it will get one. Otherwise wake it up to propagate.int ws;if (pred != head && // 上一个不为CANCELLED的节点不是head节点((ws = pred.waitStatus) == Node.SIGNAL || // 上一个不为CANCELLED的节点waitStatus 为SIGNAL -1(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) && pred.thread != null) { // 上一个不为CANCELLED的节点 thread不为nullNode next = node.next; // 本节点的nextif (next != null && next.waitStatus <= 0)compareAndSetNext(pred, predNext, next); // 上一个不为CANCELLED的节点 的next 设为为本节点的next} else {unparkSuccessor(node); // 本节点unlock}node.next = node; // help GC}
}

四、ReentrantLock的unlock方法分析AQS源码

1、unlock

public void unlock() {sync.release(1);
}
public final boolean release(int arg) {if (tryRelease(arg)) { // 释放state为0Node h = head;if (h != null && h.waitStatus != 0)unparkSuccessor(h); // 唤醒return true;}return false;
}

2、tryRelease():释放锁

protected final boolean tryRelease(int releases) {int c = getState() - releases;// 1 -1 = 0if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free = false;if (c == 0) {free = true; // 设置占用线程为nullsetExclusiveOwnerThread(null);}setState(c); state设为0return free;
}

3、unparkSuccessor():唤醒等待线程

private void unparkSuccessor(Node node) {/** If status is negative (i.e., possibly needing signal) try* to clear in anticipation of signalling.  It is OK if this* fails or if status is changed by waiting thread.*/int ws = node.waitStatus; // 头结点的waitStatue ,有线程占用的话为-1if (ws < 0) // 设为0compareAndSetWaitStatus(node, ws, 0);/** Thread to unpark is held in successor, which is normally* just the next node.  But if cancelled or apparently null,* traverse backwards from tail to find the actual* non-cancelled successor.*/Node s = node.next; // 头结点的下一个节点if (s == null || s.waitStatus > 0) {s = null;for (Node t = tail; t != null && t != node; t = t.prev)if (t.waitStatus <= 0)s = t;}if (s != null) // 唤醒下一个线程LockSupport.unpark(s.thread);
}

五、大总结

(图片模糊的话,保存下来本地看)
在这里插入图片描述

参考资料

https://zhuanlan.zhihu.com/p/378219920

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/115287.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

庆祝创造力和技术:2023年的1024程序员节

2023年的10月24日已经来临&#xff0c;这意味着我们又迎来了一年一度的程序员节。这是一个属于全球程序员社区的节日&#xff0c;一个庆祝创造力、创新和技术的时刻。无论你是一名职业程序员、技术爱好者&#xff0c;还是对编程世界感兴趣的新手&#xff0c;1024程序员节都是一…

STM32+2.9inch微雪墨水屏(电子纸)实现显示

本篇文章从硬件原理以及嵌入式编程等角度完整的介绍了墨水屏驱动过程&#xff0c;本例涉及的墨水屏为2.9inch e-Paper V2,它采用的是“微胶囊电泳显示”技术进行图像显示&#xff0c;其基本原理是悬浮在液体中的带电纳米粒子受到电场作用而产生迁移&#xff0c;从而改变显示屏各…

【C++入门到精通】哈希 (STL) _ unordered_map _ unordered_set [ C++入门 ]

阅读导航 前言一、unordered系列容器二、unordered_map1. unordered_map简介⭕函数特点 2. unordered_map接口- 构造函数- unordered_map的容量- unordered_map的迭代器- unordered_map的元素访问- unordered_map的修改操作- unordered_map的桶操作 三、unordered_set1. unorde…

react中JSX基础与useState的基本使用 + 评论显示删除需求案例

参考视频&#xff1a;https://www.bilibili.com/video/BV1ZB4y1Z7o8/?p3&spm_id_frompageDriver&vd_source5c584bd3b474d579d0bbbffdf0437c70 如果没有安装create-react-app需要先全局安装 命令&#xff1a;npm i -g create-react-app1.快速搭建开发环境 create-re…

动态天气预报:Living Weather HD for Mac

Living Weather HD能够为Mac用户提供及时、准确、个性化的天气信息&#xff0c;并提供了丰富的定制选项&#xff0c;使用户能够更加方便地查看天气状况。 具有以下特点&#xff1a; 显示世界各地的准确天气预报和当地时间。自动探测出用户所在的首个地点&#xff0c;并通过搜…

CUDA学习笔记(八)Branch Divergence and Unrolling Loop

本篇博文转载于https://www.cnblogs.com/1024incn/tag/CUDA/&#xff0c;仅用于学习。 Avoiding Branch Divergence 有时&#xff0c;控制流依赖于thread索引。同一个warp中&#xff0c;一个条件分支可能导致很差的性能。通过重新组织数据获取模式可以减少或避免warp diverge…

HarmonyOS开发:Log工具类源码分析

前言 一转眼就十月中旬了&#xff0c;国庆的劲真大&#xff0c;到现在还未缓过来&#xff0c;以至于要更新的文章迟迟未发布&#xff0c;大家可以看到&#xff0c;最近一段时间的文章&#xff0c;都是关于HarmonyOS相关的&#xff0c;两个原因吧&#xff0c;一是我司有这样的任…

解决AndroidStudio Gradle只有testDebugUnitTest

问题复现&#xff1a; 问题解决&#xff1a; 1:点击Task list not built... 2:取消勾选Configure all Gradle tasks during Gradle Sync... 大功告成&#xff0c;现在去看看Gradle&#xff0c;屏蔽的都显示出来了。

深度学习 | Pytorch深度学习实践 (Chapter 1~9)

一、overview 基于pytorch的深度学习的四个步骤基本如下&#xff1a; 二、线性模型 - Linear Model 基本概念 数据集分为测试集和训练集&#xff08;训练集、开发集&#xff09;训练集&#xff08;x&#xff0c;y&#xff09;测试集只给&#xff08;x&#xff09;过拟合&#…

解读 | 快速精确的体素GICP三维点云配准算法

原创 | 文 BFT机器人 01 摘要 本文提出了体素化广义迭代最近点&#xff08;VGICP&#xff09;算法&#xff0c;用于快速准确的三维点云配准。所提出的方法通过体素化扩展了广义迭代最近点&#xff08;GICP&#xff09;方法&#xff0c;以避免昂贵的最近邻搜索&#xff0c;同时…

Microsoft Edge浏览器中使用免费的ChatGPT

一、双击打开浏览器 找到&#xff1a;扩展&#xff0c;打开 二、打开Microsoft Edge加载项 三、Move tab新标签 获取免费ChatGPT 四、启用Move tab。启用ChatGPT。 扩展 管理扩展 启用 五、新建标签页&#xff0c;使用GPT 六、使用举例 提问 GPT回复

酷开科技 | 酷开系统,为居家生活打开更精彩的窗口

电视在我们的日常生活中扮演着重要的角色。虽然&#xff0c;作为客厅C位的扛把子——电视的娱乐作用深入人心&#xff0c;但是&#xff0c;它的涵义和影响力却因我们每个人的具体生活环境而存在着种种差异&#xff0c;而我们的生活环境又受到我们所处的社会及文化环境的影响。 …

Gartner发布2024 年十大战略技术趋势

10月17日&#xff0c;Gartner 发布2024年企业机构需要探索的****十大战略技术趋势。Gartner研究副总裁Bart Willemsen表示&#xff1a;“由于技术变革以及社会经济方面的不确定性&#xff0c;我们必须大胆采取行动并从战略上提高弹性&#xff0c;而不是采取临时措施。IT领导者的…

页面查询多项数据组合的线程池设计 | 京东云技术团队

背景 我们应对并发场景时一般会采用下面方式去预估线程池的线程数量&#xff0c;比如QPS需求是1000&#xff0c;平均每个任务需要执行的时间是t秒&#xff0c;那么我们需要的线程数是t * 1000。 但是在一些情况下&#xff0c;这个t是不好估算的&#xff0c;即便是估算出来了&…

VS Code C# 开发工具包正式发布

前言 微软于本月正式发布Visual Studio Code C#开发工具包&#xff0c;此前该开发套件已经以预览版的形式在6月份问世。经过4个月的测试和调整&#xff0c;微软修复了350多个问题&#xff0c;其中大部分是用户反馈导致的问题。此外&#xff0c;微软还对产品进行了300多项有针对…

1024 CSDN 程序员节-知存科技-基于存内计算芯片开发板验证语音识别

前言 在今年的 CSDN 程序员节上&#xff0c;我参与了这次知存科技举办的一个 AI Workshop 小活动——“基于存内计算芯片开发板验证语音识别”&#xff0c;并且有幸成为完成任务的学习者之一XD。上一次参与类似的活动是算能公司举办的“千校万里行”AIGC 大模型编译部署活动&a…

【Django 04】Serialization 序列化的高级使用

序列化器 serializers 序列化器的作用 序列化将 queryset 和 instance 转换为 json/xml/yaml 返回给前端 反序列化与序列化则相反 定义序列化器 定义类&#xff0c;继承自 Serializer 通常新建一个 serializers.py 文件 撰写序列化内容 suah as 目前只支持 read_only 只…

设计模式:外观模式(C#、JAVA、JavaScript、C++、Python、Go、PHP)

大家好&#xff01;本节主要介绍设计模式中的外观模式。 简介&#xff1a; 外观模式&#xff0c;它是一种设计模式&#xff0c;它为子系统中的一组接口提供一个统一的、简单的接口。这种模式主张按照描述和判断资料来评价课程&#xff0c;关键活动是在课程实施的全过程中进行…

科学计算语言Julia编程初步

文章目录 安装基本类型和计算函数初步条件和判断循环向量计算 Julia号称有着比肩C的速度&#xff0c;同时又像Python一样便捷的编程语言&#xff0c;非常适合科研狗使用。之前写了很多博客介绍Julia在数值分析中的应用&#xff0c;这次写一个适合初学者学习的Julia教程系列。 …

中科芯与IAR共建生态合作,IAR集成开发环境全面支持CKS32系列MCU

中国上海–2023年10月18日–嵌入式开发软件和服务的全球领导者IAR今日宣布&#xff0c;与中科芯集成电路有限公司&#xff08;以下简称中科芯&#xff09;达成生态合作&#xff0c;IAR已全面支持CKS32系列MCU的应用开发。这一合作将进一步推动嵌入式系统的发展&#xff0c;并为…