AQS详解

article/2025/10/7 19:16:03

AQS是AbstractQueuedSynchronizer的简称。AQS提供了一种实现阻塞锁和一系列依赖FIFO等待队列的同步器的框架,如下图所示。AQS为一系列同步器依赖于一个单独的原子变量(state)的同步器提供了一个非常有用的基础。子类们必须定义改变state变量的protected方法,这些方法定义了state是如何被获取或释放的。鉴于此,本类中的其他方法执行所有的排队和阻塞机制。子类也可以维护其他的state变量,但是为了保证同步,必须原子地操作这些变量。

在这里插入图片描述
使用一个volatile的int类型的state表示同步状态,通过内置的FIFO队列CLH完成资源获取的排队工作,将资源封装为Node,通过cas改变state值

AQS同时提供了互斥模式(exclusive)和共享模式(shared)两种不同的同步逻辑。一般情况下,子类只需要根据需求实现其中一种模式,当然也有同时实现两种模式的同步类,如ReadWriteLock。

state状态

AbstractQueuedSynchronizer维护了一个volatile int类型的变量,用户表示当前同步状态。volatile虽然不能保证操作的原子性,但是保证了当前变量state的可见性。

    /*** The synchronization state.*/private volatile int state;/*** Returns the current value of synchronization state.* This operation has memory semantics of a {@code volatile} read.* @return current state value*/protected final int getState() {return state;}/*** Sets the value of synchronization state.* This operation has memory semantics of a {@code volatile} write.* @param newState the new state value*/protected final void setState(int newState) {state = newState;}/*** Atomically sets synchronization state to the given updated* value if the current state value equals the expected value.* This operation has memory semantics of a {@code volatile} read* and write.** @param expect the expected value* @param update the new value* @return {@code true} if successful. False return indicates that the actual*         value was not equal to the expected value.*/protected final boolean compareAndSetState(int expect, int update) {// See below for intrinsics setup to support thisreturn unsafe.compareAndSwapInt(this, stateOffset, expect, update);}

自定义资源共享方式

AQS定义两种资源共享方式:Exclusive(独占,只有一个线程能执行,如ReentrantLock)和Share(共享,多个线程可同时执行,如Semaphore/CountDownLatch)

不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源state的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。自定义同步器实现时主要实现以下几种方法:

isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。
tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。
tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。
tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。

源码实现

1.Lock

ReentrantLock -> Sync -> NonfairSync(非公平) -> acquire -> tryAcquire/addWaiter/acquireQueued/selfInterrupt

    static final class NonfairSync extends Sync {private static final long serialVersionUID = 7316153563782823691L;/*** Performs lock.  Try immediate barge, backing up to normal* acquire on failure.*/final void lock() {if (compareAndSetState(0, 1))setExclusiveOwnerThread(Thread.currentThread());elseacquire(1);}protected final boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires);}}
    public final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();}

通过注释我们知道,acquire方法是一种互斥模式,且忽略中断。该方法至少执行一次tryAcquire(int)方法,如果tryAcquire(int)方法返回true,则acquire直接返回,否则当前线程需要进入队列进行排队。函数流程如下:

  1. tryAcquire()尝试直接去获取资源,如果成功则直接返回;
  2. addWaiter()将该线程加入等待队列的尾部,并标记为独占模式;
  3. acquireQueued()使线程在等待队列中获取资源,一直获取到资源后才返回。如果在整个等待过程中被中断过,则返回true,否则返回false。
  4. 如果线程在等待过程中被中断过,它是不响应的。只是获取资源后才再进行自我中断selfInterrupt(),将中断补上。

tryAcquire

    protected boolean tryAcquire(int arg) {throw new UnsupportedOperationException();}

子类继承,没有实现的话直接抛出异常

在这里插入图片描述

        final boolean nonfairTryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {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);return true;}return false;}

1.判断状态位是否为0,0是可以占用,如果是0的话占用,不是0的话返回false
2.判断当前线程是否为得到位置的线程,比如如果前一个线程走了,然后又回来有点事情的话,那么返回false

addWaiter

添加到队列的过程

    private Node addWaiter(Node mode) {Node node = new Node(Thread.currentThread(), mode);// Try the fast path of enq; backup to full enq on failureNode pred = tail;if (pred != null) {node.prev = pred;if (compareAndSetTail(pred, node)) {pred.next = node;return node;}}enq(node);return node;}

第一次队列无Node的时候返回直接进入enq()方法,如果有Node话会进入if
if中有一个cas操作,比较和交换了头指针和当前Node。
使得当前进入的第三个Node和第二个Node接在了一起
在这里插入图片描述

enq

    private Node enq(final Node node) {for (;;) {Node t = tail;if (t == null) { // Must initializeif (compareAndSetHead(new Node()))tail = head;} else {node.prev = t;if (compareAndSetTail(t, node)) {t.next = node;return t;}}}}

这里的一开始的头指针设置为空Node作为占位符。是傀儡节点,哨兵节点。
第二次进入的时候第二个节点的头节点指的是空节点,然后cas使得尾结点指向第二个节点。
哨兵节点的下一个节点指向第二个节点。于是形成了一个双向链表。

在这里插入图片描述

acquireQueued

    final boolean acquireQueued(final Node node, int 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);}}

acquireQueued()用于队列中的线程自旋地以独占且不可中断的方式获取同步状态(acquire),直到拿到锁之后再返回。该方法的实现分成两部分:如果当前节点已经成为头结点,尝试获取锁(tryAcquire)成功,然后返回;否则检查当前节点是否应该被park,然后将该线程park并且检查当前线程是否被可以被中断。

shouldParkAfterFailedAcquire

shouldParkAfterFailedAcquire方法通过对当前节点的前一个节点的状态进行判断,对当前节点做出不同的操作,至于每个Node的状态表示。也是为了解决哨兵节点的waitState从0改为-1。于是可以操作后面的节点。判断后面的节点是否进行park

    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {// 获取当前节点状态int ws = pred.waitStatus;// 如果是SIGNAL的话,线程被释放,返回trueif (ws == Node.SIGNAL)return true;// ws大于0表示ws = 1if (ws > 0) {do {node.prev = pred = pred.prev;} while (pred.waitStatus > 0);pred.next = node;} else {compareAndSetWaitStatus(pred, ws, Node.SIGNAL);}return false;}

parkAndCheckInterrupt()

该方法让线程去休息,真正进入等待状态。park()会让当前线程进入waiting状态。在此状态下,有两种途径可以唤醒该线程:1)被unpark();2)被interrupt()。需要注意的是,Thread.interrupted()会清除当前线程的中断标记位。

    private final boolean parkAndCheckInterrupt() {LockSupport.park(this);return Thread.interrupted();}

这个方法才是真正让线程进入等待状态进入waiting状态。
1.自己unpark 2.被中断

总结

  1. 调用自定义同步器的tryAcquire()尝试直接去获取资源,如果成功则直接返回;
  2. 没成功,则addWaiter()将该线程加入等待队列的尾部,并标记为独占模式;
  3. acquireQueued()使线程在等待队列中休息,有机会时(轮到自己,会被unpark())会去尝试获取资源。获取到资源后才返回。如果在整个等待过程中被中断过,则返回true,否则返回false。
  4. 如果线程在等待过程中被中断过,它是不响应的。只是获取资源后才再进行自我中断selfInterrupt(),将中断补上。

2.unlock

unlock -> release -> tryrelease ->

    public void unlock() {sync.release(1);}

release

    public final boolean release(int arg) {if (tryRelease(arg)) {Node h = head;if (h != null && h.waitStatus != 0)unparkSuccessor(h);return true;}return false;}

tryRelease

        protected final boolean tryRelease(int releases) {int c = getState() - releases;if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free = false;if (c == 0) {free = true;setExclusiveOwnerThread(null);}setState(c);return free;}

与acquire()方法中的tryAcquire()类似,tryRelease()方法也是需要独占模式的自定义同步器去实现的。正常来说,tryRelease()都会成功的,因为这是独占模式,该线程来释放资源,那么它肯定已经拿到独占资源了,直接减掉相应量的资源即可(state-=arg),也不需要考虑线程安全的问题。但要注意它的返回值,上面已经提到了,release()是根据tryRelease()的返回值来判断该线程是否已经完成释放掉资源了!所以自义定同步器在实现时,如果已经彻底释放资源(state=0),要返回true,否则返回false。
  unparkSuccessor(Node)方法用于唤醒等待队列中下一个线程。这里要注意的是,下一个线程并不一定是当前节点的next节点,而是下一个可以用来唤醒的线程,如果这个节点存在,调用unpark()方法唤醒。
  总之,release()是独占模式下线程释放共享资源的顶层入口。它会释放指定量的资源,如果彻底释放了(即state=0),它会唤醒等待队列里的其他线程来获取资源。


http://chatgpt.dhexx.cn/article/yWiG2faF.shtml

相关文章

一文让你彻底搞懂AQS(通俗易懂的AQS)

一文让你彻底搞懂AQS(通俗易懂的AQS) 一、什么是AQS AQS是一个用来构建锁和同步器的框架&#xff0c;使用AQS能简单且高效地构造出应用广泛的大量的同步器&#xff0c;比如我们提到的ReentrantLock&#xff0c;Semaphore&#xff0c;其他的诸如ReentrantReadWriteLock&#x…

什么是AQS

AQS ( Abstract Queued Synchronizer &#xff09;是一个抽象的队列同步器&#xff0c;通过维护一个共享资源状态&#xff08; Volatile Int State &#xff09;和一个先进先出&#xff08; FIFO &#xff09;的线程等待队列来实现一个多线程访问共享资源的同步框架。 一、AQS…

泛函分析之变分法

泛函数 以上截图来自于《变分法简介Part 1.&#xff08;Calculus of Variations&#xff09;》 变分法 研究泛函极值的方法就是所谓变分法。 以上截图来自于《最速降线的数学模型—变分法》 欧拉-拉格朗日方程

mathematica变分法和样条插值求解最小旋转曲面

mathematica求解最小面积旋转曲面 做你没做过的事叫成长&#xff0c;做你不愿做做的事叫改变&#xff0c;做你不敢做的事叫突破。—— 巴菲特 问题描述&#xff1a; 在一条直线的同一侧有两个已知点&#xff0c;试找出一条连接这两点的曲线&#xff0c;使这条曲线绕直线旋转所…

变分法模型的运用:生产设备的最大经济效益

上一节介绍了 动态优化模型/ 变分法 的基本思想&#xff0c;本节将一个变分法的运用。 目录 1 问题分析与假设 2 模型构造 3 模型求解 变分法习题 某工厂购买了一台新设备投入到生产中。一方面该设备随着运行时间的推…

简述变分法在泛函极值问题中的应用

此文主要有两部分内容&#xff0c;一部分是泛函的一些基本概念&#xff1b;第二部分是变分法在研究泛函极值问题中的应用。 第一部分 泛函 泛函是函数概念的一种扩充&#xff0c;函数描述的是从数到数的对应关系&#xff0c;从自变量到因变量的一种对应关系&#xff1b;而泛函…

变分法(欧拉 - 拉格朗日)和梯度下降求泛函最优解

泛函的简单理解&#xff1a; 是的变量&#xff0c; 这样的就叫泛函 . 加个积分&#xff0c;这样的就叫积分泛函 . 欧拉 - 拉格朗日 (E - L) 公式&#xff1a; 定义一个能量泛函如下&#xff1a; 我们的目的是找到能使 取到极值的时候 的取值&#xff0c;所以我们就假设 就…

第二章-最优控制中的变分法(经典变分法或古典变分法)1

是《最优控制理论与应用(邵克勇&#xff0c;王婷婷&#xff0c;宋金波)》的读书笔记&#xff0c;相比于其他的书&#xff0c;选择这本书的理由是页数少&#xff0c;能读完。解学书的《最优控制理论与应用》看目录感觉很全&#xff0c;但是太厚了&#xff0c;感觉看不完。 虽然…

变分法理解1——泛函简介

变分法是处理泛函的数学领域&#xff0c;和处理函数的传统微积分相对。 对泛函求极值的问题称为变分问题&#xff0c;使泛函取极值的函数称为变分问题的解&#xff0c;也称为极值函数。 传统的微积分中的一个常见的问题是找到一个 x x x 值使得 y ( x ) y(x) y(x) 取得最大值…

变分法证明两点之间线段最短

传送门https://zhuanlan.zhihu.com/yueaptx 变分法简介Part 1.&#xff08;Calculus of Variations&#xff09; Dr.Stein 计算力学 ​关注他 283 人赞了该文章 泛函数 (Functionals) 简而言之&#xff0c;泛函数是函数的函数&#xff0c;即它的输入是函数&#xff0c;输出…

最优控制理论 一、变分法和泛函极值问题

变分法是最优控制问题的三大基石之一&#xff0c;下面讨论一些变分法的常用理论。 1. 性能指标泛函 无约束最优控制问题&#xff0c;若固定起止时间&#xff0c;两端状态固定&#xff0c;即 x ( 0 ) x 0 , x ( t f ) x f , t ∈ [ 0 , t f ] x(0)x_0, x(t_f)x_f, t\in[0,t…

[变分法介绍]优美的旋轮线:最速下降线问题,通过费马光学原理的初等证明

[变分法介绍]优美的旋轮线:最速下降线问题,通过费马光学原理的初等证明 变分法 费马光学原理最速下降线问题旋轮线旋轮线最速下降性质的证明一些旋轮线及变形参考书目:1696年约翰伯努利在写给他哥哥雅克布伯努利的一封公开信中提出了如下的“捷线”问题:设想一个质点沿连接…

深入浅出解析变分法——一种常用的数学方法

前言&#xff1a;笔者从事图像处理行业&#xff0c;总是接触到变分法这个概念&#xff0c;一直没有很深入的去理解这个概念&#xff0c;同时我看其他大佬的博文也比较糊涂&#xff0c;因此最近花了一些时间好好梳理了这部分数学知识。文章共3部分&#xff0c;主要是对变分法的解…

变分法:在图像处理中的应用(一)

前言 最近学习稠密重建的相关知识&#xff0c;发现变分法通常作为一个平滑的正则项出现在残差平方和的损失函数中。而图像处理中又经常出现这类最小损失函数的优化问题&#xff0c;如图像分割、稠密光流、稠密重建等等&#xff0c;这些优化问题中都有可能涉及到变分法。因此&am…

电磁仿真原理——3. 变分法(Variationl Methods)

目录 引言线性空间的算子问题变分的计算问题欧拉公式 构造泛函的方法利用分部积分构造利用标准变分原理构造 瑞利一里茨法加权留数法本征问题变分的实际应用 引言 由于课程后面重点的矩量法和有限元法都是基于变分法进行的&#xff0c;变分法是它们的数学基础&#xff0c;实际…

泛函极值问题与变分法

泛函与泛函极值问题 平面内两点A&#xff0c;B&#xff0c;连接两点之间的曲线有很多种方式。分别用函数 f i ( x ) f_{i}(x) fi​(x)来表示。对于给定的曲线 f i ( x ) f_{i}(x) fi​(x), 那么两点之间连线的长度可以表示为 J ( f i ( x ) ) ∫ A B 1 f i ′ ( x ) 2 d x J…

【Matlab】变分法求控制器(无约束)

在动态最优控制中&#xff0c;目标函数是一个泛函数&#xff0c;求解动态最优化问题可以看做是求泛函极值的问题&#xff0c;求解泛函极值有一个方法&#xff0c;即变分法&#xff0c;本文章便介绍有关变分法的一些自己的学习理解。 变分法的基本概念 泛函 如果一个因变量的…

关于变分法

在介绍变分贝叶斯之前&#xff0c;首先以这篇博客介绍下大名鼎鼎的变分法。 参考资料主要是知乎的文章与维基百科。 变分就是函数的微分。 回顾一下传统的函数优化问题。 对于 min ⁡ x f ( x ) \min_x f(x) minx​f(x)这样的优化问题&#xff0c;求取最优的 x x x的做法常用…

变分法

变分法 弦平衡方程的导出&#xff0c;建立起横向位移u&#xff0c;张力T&#xff0c;外力f之间的关系&#xff1a; 方一、根据受力平衡导出 推导时用的技巧或假设&#xff1a; 1.泰勒展开近似 同理 2. 3.小变形假设&#xff0c;张力均匀&#xff0c;即 4.方程推导中忽略二…

变分法入门介绍

文章目录 变分法入门介绍泛函和变分法变分法求泛函极值变分的定义拉格朗日函数欧拉方程 案例分析--两点之间直线最短在Mathematica中使用变分法参考文献 变分法入门介绍 读完这篇博文你可以了解变分的基本概念&#xff0c;以及使用变分法求解最简泛函的极值。本文没有严密的数…