Java技术之AQS详解

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

在这里插入图片描述

AbstractQueuedSynchronizer

简写为AQS,抽象队列同步器。它是一个用于构建锁和同步器的框架,许多同步器都可以通过AQS很容易并且高效的构造出来,以下都是通过AQS构造出来的:ReentrantLock, ReentrantReadWriteLock

AQS使用了模板方法,把同步队列都封装起来了,同时提供了以下五个未实现的方法,用于子类的重写:

  • boolean tryAcquire(int arg):尝试以独占模式进行获取。
    此方法应查询对象的状态是否允许以独占模式获取对象,如果允许则获取它。如果获取失败,则将当前线程加入到等待队列,直到其他线程唤醒。
  • boolean tryRelease(int arg):尝试以独占模式释放锁。
  • int tryAcquireShared(int
    arg):尝试以共享模式获取锁,此方法应查询对象的状态是否允许以共享模式获取对象,如果允许则获取它。如果获取失败,则将当期线程加入到等待队列,直到其他线程唤醒。
  • boolean tryReleaseShared(int arg):尝试以共享模式释放锁。
  • boolean isHeldExclusively():是否独占模式
    在这里插入图片描述
  • state:所有线程通过通过CAS尝试给state设值,当state>0时表示被线程占用;同一个线程多次获取state,会叠加state的值,从而实现了可重入;
  • exclusiveOwnerThread:在独占模式下该属性会用到,当线程尝试以独占模式成功给state设值,该线程会把自己设置到exclusiveOwnerThread变量中,表明当前的state被当前线程独占了;
  • 等待队列(同步队列):等待队列中存放了所有争夺state失败的线程,是一个双向链表结构。state被某一个线程占用之后,其他线程会进入等待队列;一旦state被释放(state=0),则释放state的线程会唤醒等待队列中的线程继续尝试cas设值state;
  • head:指向等待队列的头节点,延迟初始化,除了初始化之外,只能通过setHead方法进行修改;
  • tail:指向等待队列的队尾,延迟初始化,只能通过enq方法修改tail,该方法主要是往队列后面添加等待节点。

AQS队列节点数据结构

在这里插入图片描述

AQS中的一般处理流程

1.public final void acquire(int arg)
这个方法是使用独占模式获取锁,忽略中断。通过至少调用一次tryAcquire成功返回来实现。 否则,线程将排队,并可能反复阻塞和解除阻塞,并调用tryAcquire直到成功。

public abstract class AbstractQueuedSynchronizerextends AbstractOwnableSynchronizerimplements java.io.Serializable {public final void acquire(int arg) {// 尝试获取锁,这里是一个在AQS中未实现的方法,具体由子类实现if (!tryAcquire(arg) &&  // 获取不到锁,则 1.添加到等待队列 2.不断循环等待重试acquireQueued(addWaiter(Node.EXCLUSIVE), arg))  selfInterrupt();}
}

2.tryAcquire(int arg)

— 非公平锁在调用 lock 后,首先就会调用 CAS 进行一次抢锁,如果这个时候恰巧锁没有被占用,那么直接就获取到锁返回了。 非公平锁在CAS 失败后,和公平锁一样都会进入到 tryAcquire 方法,在 tryAcquire 方法中,如果发现锁这个时候被释放了(state== 0),非公平锁会直接 CAS 抢锁,但是公平锁会判断等待队列是否有线程处于等待状态,如果有则不去抢锁,乖乖排到后面。

公平锁和非公平锁就这两点区别,如果这两次 CAS 都不成功,那么后面非公平锁和公平锁是一样的,都要进入到阻塞队列等待唤醒。

相对来说,非公平锁会有更好的性能,因为它的吞吐量比较大。当然,非公平锁让获取锁的时间变得更加不确定,可能会导致在阻塞队列中的线程长期处于饥饿状态。

一开始,会尝试调用AQS中未实现的方法tryAcquire()尝试获取锁,获取成功则表示获取锁了,该方法的实现一般通过CAS进行设置state尝试获取锁:
在这里插入图片描述
不同的锁可以有不同的tryAcquire()实现,所以,你可以看到ReentrantLock锁里面会有非公平锁和公平锁的实现方式。

ReentrantLock公平锁的实现代码在获取锁之前多了一个判断:!hasQueuedPredecessors(),这个是判断如果当前线程节点之前没有其他节点了,那么我们才可以尝试获取锁,这就是公平锁的体现。

3. private Node addWaiter(Node mode)
获取锁失败之后,则会进入这一步,这里会尝试把线程节点追加到等待队列后面,是通过CAS进行追加的,追加失败的情况下,会循环重试,直至追加成功为止。如果追加的时候,发现head节点还不存在,则先初始化一个head节点,然后追加上去:

public abstract class AbstractQueuedSynchronizerextends AbstractOwnableSynchronizerimplements java.io.Serializable {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;if (pred != null) {// 将原来尾节点设置为新节点的上一个节点node.prev = pred;// 尝试用新节点取代原来的尾节点if (compareAndSetTail(pred, node)) {// 取代成功,则将原来尾指针的下一个节点指向新节点pred.next = node;return node;}}// 如果当前尾指针为空,则调用enq方法enq(node);return node;}
}

4.final boolean acquireQueued(final Node node, int arg)
加入等待队列之后,会执行该方法,不断循环地判断当前线程节点是否在head后面一位,如果是则调用tryAcquire()获取锁,如果获取成功,则把线程节点作为Node head,并把原Node head的next设置为空,断开原来的Node head。注意这个Node head只是占位作用,每次处理的都是Node head的下一个节点:

public abstract class AbstractQueuedSynchronizerextends AbstractOwnableSynchronizerimplements java.io.Serializable {/*** 已经入队的线程尝试获取锁*/ final boolean acquireQueued(final Node node, int arg) {//标记是否成功获取锁boolean failed = true;try {//标记线程是否被中断过boolean interrupted = false;for (;;) {//获取前驱节点final Node p = node.predecessor();//如果前驱是head,即该结点已成老二,那么便有资格去尝试获取锁if (p == head && tryAcquire(arg)) {// 获取成功,将当前节点设置为head节点setHead(node);// 原head节点出队,在某个时间点被GC回收p.next = null; // help GC//获取成功failed = false;//返回是否被中断过return interrupted;}// 判断是否需要阻塞线程,该方法中会把取消状态的节点移除掉,并且把当前节点的前一个节点设置为SIGNAL// 判断获取失败后是否可以挂起,若可以则挂起if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())// 线程若被中断,设置interrupted为trueinterrupted = true;}} finally {if (failed)cancelAcquire(node);}}
}

如果当前节点的pre不是head,或者争抢失败,则会将前面节点的状态设置为SIGNAL。
如果前面的节点状态大于0,表示节点被取消,这个时候会把该节点从队列中移除掉。
下图为尝试CAS争抢锁,但失败了,然后把head节点状态设置为SIGNAL:
在这里插入图片描述
然后再会循环一次尝试获取锁,如果获取失败了,就调用LockSupport.park(this)挂起线程。

那么时候才会触发唤起线程呢?这个时候我们得先看看释放锁是怎么做的了。
5.public final boolean release(int arg)

public abstract class AbstractQueuedSynchronizerextends AbstractOwnableSynchronizerimplements java.io.Serializable {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()具体由子类实现。一般处理流程是让state减1。

如果释放锁成功,并且头节点waitStatus!=0,那么会调用unparkSuccessor()通知唤醒后续的线程节点进行处理。

注意:在遍历队列查找唤醒下一个节点的过程中,如果发现下一个节点状态是CANCELLED那么就会忽略这个节点,然后从队列尾部向前遍历,找到与头结点最近的没有被取消的节点进行唤醒操作。
在这里插入图片描述
唤醒之后,节点对应的线程2又从acquireQueued()方法的阻塞处醒来继续参与争抢锁。并且争抢成功了,那么会把head节点的下一个节点设置为null,让自己所处的节点变为head节点:
在这里插入图片描述
这样一个AQS独占式、非中断的抢占锁的流程就结束了。
6.完整流程
最后我们再以另一个维度的流程来演示下这个过程。

首先有4个线程争抢锁,线程1,成功了,其他三个失败了,分别依次入等待队列:在这里插入图片描述
线程2、线程3依次入队列:在这里插入图片描述
现在突然发生了点事情,假设线程3用的是带有超时时间的tryLock,超过了等待时间,线程3状态变为取消状态了,这个时候,线程4追加到等待队列中后,发现前一个节点的状态是1取消状态,那么会执行操作把线程3节点从队列中移除掉:
在这里插入图片描述
最后,线程1释放了锁,然后把head节点ws设置为0,并且找到了离head最靠近的一个waitStatus<=0的线程并唤醒,然后参与竞争获取锁:在这里插入图片描述
最终,线程2获取到了锁,然后把自己变为了Head节点,并取代了原来的Head节点:在这里插入图片描述

参考
https://blog.csdn.net/a724888/article/details/60955965


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

相关文章

(面经总结)一篇文章带你完整复习 Java 中的 AQS

文章目录 一、什么是AQS二、AQS的原理三、state:状态四、AQS共享资源的方式:独占式和共享式一、什么是AQS AQS(Abstract Queued Synchronizer)是一个抽象的队列同步器,通过维护一个共享资源状态(Volatile Int State)和一个先进先出(FIFO)的线程等待队列来实现一个多线…

AQS详细大分解,彻底弄懂AQS

AQS深入分析总结 AQS 很久之前便写了这篇文章&#xff0c;一直没有时间发出来&#xff0c;文章如果有写的不好的地方&#xff0c;欢迎大家能够指正&#xff0c;下面开始详细分析介绍&#xff0c;希望大家能够耐心读下去&#xff0c;肯定会受益匪浅的&#xff0c;AQS是Java JU…

AQS详解

AQS是AbstractQueuedSynchronizer的简称。AQS提供了一种实现阻塞锁和一系列依赖FIFO等待队列的同步器的框架&#xff0c;如下图所示。AQS为一系列同步器依赖于一个单独的原子变量&#xff08;state&#xff09;的同步器提供了一个非常有用的基础。子类们必须定义改变state变量的…

一文让你彻底搞懂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;本文章便介绍有关变分法的一些自己的学习理解。 变分法的基本概念 泛函 如果一个因变量的…