真正学透AQS

article/2025/10/7 17:09:34

在java的并发中,我们会接触到很多工具类,比如说ReentranLock,CountDownLatch,Semaphore,Condition。而这些工具类都是同门师兄弟来的,它们共同的师父就是我们这次文章讲的AQS,全名:AbstractQueuedSynchronizer。这个AQS几乎是java里面所有同步器和锁实现所依赖的框架。在面试中,也会有不少面试官会问到这个AQS。假如说你可以很明白的讲解出来AQS,那可是加分项。

在学习一个新框架或者新技术的时候,我们其实可以围绕3个点来去了解。

  1. 这个技术框架定义是什么?

  2. 有什么作用?

  3. 实现原理是怎么样的?

简称学习的三板斧!只要把这3个点弄明白了。那就算了解的七七八八了,可以添加到自己的知识库。后面再进行实践去熟悉和沉淀下来,融会贯通,能够学以致用了。

那我们先来了解一下AQS是什么。从源码开始。我从该类的注释里面直接截图给大家看看。

简陋的翻译一下:提供了一个依靠先进先出的等待队列来实现阻塞锁和同步器的框架。这个类设计目的就是设计出一个依靠简单的原子性状态标记实现,成为大多数各种各样的同步器的有用的基础框架。子类必须定义改变状态标记的方法。该方法包括了状态被获取和释放的规则。有了这2个方法,再加上这个类的其他方法就可以完成所有队列和阻塞器。子类可以包含其他状态值,但是只能使用getState,setState和compareAndSetState这几个方法 才能达到实现同步的目的。

顺便提一下,我们看源码的时候,可以通过阅读类的注释就可以知道这个类的设计思路,而很多网上对一些技术的科普,不少是直接从源码的英文注释翻译过来的。所以,只要我们把这些介绍的英文啃下来,弄明白。那就是最好了解该知识点的方式。

通过这里的定义,我们是可以知道AQS的设计目的了。而作用也说明白了。就是作为并发同步器和锁的实现的基础框架。那接下来,我们来了解实现原理。

AQS实现中有2个关键点,一个是锁的状态字段state,一个由链表实现的先进先出队列。state用来表明锁的状态,而队列用于等待线程的排队。在AQS中实现了setState,getState这些状态相关的方法,还有队列里类似新增节点,移除节点的方法,等等。

然后AQS是一个抽象类,给出了需要子类实现的方法,其他锁和相关同步器只需要重写父类的方法就可以实现自己的锁和同步器功能。AQS提供了独占模式和共享模式这两种模式。也就是我们经常说的独占锁和共享锁的来源。以下是AQS框架需要子类实现的方法:

tryAcquire //获取锁
tryRelease //释放锁
tryAcquireShared //获取共享锁
tryReleaseShared //释放共享锁
isHeldExclusively //是否拥有独占锁

为了更好的理解这几个方法,我们从ReentrantLock可重入锁的源码来看看它是怎么重写AQS的方法来实现锁的。

ReentrantLock中呢,也是提供了公平锁和非公平锁的两种模式。默认没有传参数就是非公平锁。

 public ReentrantLock() {//不传参数就是新建一个非公平锁sync = new NonfairSync();}public ReentrantLock(boolean fair) {//可以通过传参数来选择公平锁sync = fair ? new FairSync() : new NonfairSync();}

其中Sync是ReentrantLock的一个内部类,它继承了AQS。

NonfairSync类和FairSync类都是是继承Sync的子类,tryAcquire()是AQS中获取锁的方法。FairSync重写了tryAcquire()方法,而NonfairSync是使用了父类Sync的tryAcquire()方法。

我们先来看看不公平锁NonFairSync的tryAcquire()方法:

protected final boolean tryAcquire(int acquires) {//调用父类的方法return nonfairTryAcquire(acquires);}
final boolean nonfairTryAcquire(int acquires) {//定义不可改变的变量存储当前线程final Thread current = Thread.currentThread();//获取AQS中的状态属性int c = getState();//判断如果是0的话,说明还没有其他线程获取锁,那可以尝试获取锁if (c == 0) {//使用AQS的compareAndSetState改变state标识,因为可能有多个//线程同时获取锁,所以在判断后还是需要调用compareAndSetState//来改变状态,compareAndSetState实现是unsafe类来实现的,我//们可以默认为是线程安全的。if (compareAndSetState(0, acquires)) {//设置当前线程为得到锁的线程。setExclusiveOwnerThread(current);return true;}}//可重复入的判断来了,假如已经获取锁的线程再次进来的时候就会//发现自己就是得到该锁的线程。else if (current == getExclusiveOwnerThread()) {//将state加上acquires,也就是每重新进来一次,都加1.             int nextc = c + acquires;//假如超出了int的正数的范围就报错if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;}

不公平锁这里的实现是根据AQS的state来实现的。在AQS的定义中,state是一个重要的属性。这个state是线程占有的标识,通过state这个标识来控制线程不被其他线程影响。在独占模式下,当其他线程获取到state是已经修改的,那说明已经有第一个线程在执行代码了,这时候就不能再执行了。

那我们再来看看公平锁的tryAcquire()实现。

protected final boolean tryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();//还是先判断状态属性值if (c == 0) {//唯一的不同点在这里,多一个判断,hasQueuedPredecessors,//判断是否是队列里面有在排队的线程if (!hasQueuedPredecessors() &&compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;if (nextc < 0)throw new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;}

我们可以看到,公平锁就是需要从队列里面取下一个线程执行,而这个队列是先进先出的,所以保证公平。

我们再来看看释放锁的tryRelease()方法,NonfairSync类和FairSync类中,释放锁的方法都是使用父类Sync的方法。

protected final boolean tryRelease(int releases) {//释放锁的时候需要传释放锁的次数,然后在拿state减去需要释放//锁的次数,然后得出state的新的值int c = getState() - releases;//假如释放锁的线程不是占有该锁的线程,则抛异常if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free = false;//当锁被释放到没有占有时,则返回true,说明锁是没有了,然后把//占有锁的线程值给置空if (c == 0) {free = true;setExclusiveOwnerThread(null);}setState(c);return free;}

到这里,我们就知道ReentrantLock是怎么重写获取锁和释放锁的方法了。其中像setState,setExclusiveOwnerThread都是AQS中的方法。不需要ReentranLock这边去实现的。那ReentranLock是重写了AQS中的tryAcquire和tryRelease就可以实现自己的功能。

我们来看看另外一个使用tryAcquireShared和tryReleaseShared共享锁来实现自己功能的并发工具类:CountDownLatch。

CountDownLatch的作用是可以让指定数量的线程完成任务后,主线程再进行下一步操作。常用的方法就是

CountDownLatch.countDown()

CountDownLatch.await()

我们就从这2个方法来入手,首先是countDown()

public void countDown() {//releaseShared是父类AQS实现的方法。sync.releaseShared(1);}

CountDownLatch这里也是使用了一个内部类Sync去继承AQS。

public final boolean releaseShared(int arg) {//先释放共享锁,这里使用的是重写的tryReleaseSharedif (tryReleaseShared(arg)) {//父类AQS实现方法,这个方法就是完成在释放锁后,把//队列里面的线程释放出来获取锁,但是在CountDownLatch//中其实没有使用到这个队列。doReleaseShared();return true;}return false;}
protected int tryAcquireShared(int acquires) {return (getState() == 0) ? 1 : -1;}
protected boolean tryReleaseShared(int releases) {// Decrement count; signal when transition to zero//这里的注解也说明了。这里是做减一操作 ,当状态值state为0//则返回true,说明现在CountDownLatch中的线程都已经执行完了for (;;) {int c = getState();if (c == 0)return false;int nextc = c-1;if (compareAndSetState(c, nextc))//如果减到0时,立即判断返回,不需要再循环一次。return nextc == 0;}}

countDown方法简单来说就是对state进行减一。我们来看看await()方法。

public void await() throws InterruptedException {sync.acquireSharedInterruptibly(1);}
//acquireSharedInterruptibly是AQS的方法,这里也是调用了
//tryAcquireShared方法
public final void acquireSharedInterruptibly(int arg)throws InterruptedException {if (Thread.interrupted())throw new InterruptedException();if (tryAcquireShared(arg) < 0)//父类方法doAcquireSharedInterruptibly(arg);}    
/*** Acquires in shared interruptible mode.* @param arg the acquire argument*/private void doAcquireSharedInterruptibly(int arg)throws InterruptedException {final Node node = addWaiter(Node.SHARED);boolean failed = true;try {for (;;) {final Node p = node.predecessor();if (p == head) {int r = tryAcquireShared(arg);//循环获取state,判断是否已经被释放。//释放则returnif (r >= 0) {setHeadAndPropagate(node, r);p.next = null; // help GCfailed = false;return;}}if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())throw new InterruptedException();}} finally {if (failed)cancelAcquire(node);}}

在await()方法中就是会调用到AQS中的doAcquireSharedInterruptibly方法,这个方法中关键的地方我已经注释了。其实就是当state变为0的时候会跳出循环。在CountDownLatch的效果就变成了线程都执行完了。所以简单来说CountDownLatch在一开始的时候就按照传入的参数,假如是3,把3赋值到state中,那每一个执行结束都把state减一。那等state等于0的时候就说明都执行完了。可以放行了。

通过这2个并发类,现在我们也对AQS的实现有了深刻印象了吧,AQS就是state和队列来实现。不过在这篇文章中,没有对AQS中的所有方法都进行分析一遍,这需要读者自己去阅读源码。不是作者懒,而是我觉得要想真正的把这些知识沉淀成自己的知识,是需要自己去实践,去阅读,才会深深的印在自己的脑海中。

为什么有时候你看了文章,以为自己懂了某个知识点,但是在面试的时候说不清楚呢?就是因为你的知识不还不扎实。而让知识扎实有挺多途径,比如说你把知识点都背下来,但是假如是自己去理解阅读的,再加以总结,那会比背下来的知识点更加好。也能更加好的领悟作者的思路。

 

授人以鱼不如授人以渔,小侠与你下篇文章见~

公众号:易小侠的Code


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

相关文章

AQS介绍

目录 1. AQS简介:2. AQS的原理&#xff1a;3. 和AQS相关的类&#xff1a;AQS能干嘛&#xff1a;AQS初步&#xff1a;AQS内部体系架构: 1. AQS简介: AQS是什么&#xff1a; AQS全名&#xff1a;AbstractQueuedSynchronizer即抽象的队列同步器&#xff0c;这个类在java.util.co…

如何理解AQS

AQS核心数据结构 AQS内部主要维护了一个FIFO&#xff08;先进先出&#xff09;的双向链表。 AQS数据结构原理 AQS内部维护的双向链表中的各个节点分别指向直接的前驱节点和直接的后续节点。所以&#xff0c;在AQS内部维护的双向链表可以从其中的任意一个节点遍历前驱结点和后…

AQS解析

Java中的大部分同步类&#xff08;Lock、Semaphore、ReentrantLock等&#xff09;都是基于AbstractQueuedSynchronizer&#xff08;简称为AQS&#xff09;实现的。AQS是一种提供了原子式管理同步状态、阻塞和唤醒线程功能以及队列模型的简单框架。本文会从应用层逐渐深入到原理…

AQS机制

1、什么是AQS&#xff1f; AQS是抽象同步队列&#xff0c;基于CAS和LockSupport实现&#xff0c;通过资源状态state和AQS的同步队列实现线程抢占资源的管理。 2、获取资源 线程进来先获取资源&#xff0c;如果失败会重试一次&#xff0c;再次失败会将当前线程存放至…

AQS原理

AQS是一个构建锁和同步器的并发框架&#xff0c;是AbstractQueuedSynchronizer的缩写&#xff0c;常见AQS实现的同步器框架有ReentrantLock,Semaphore,Latch,Barrier,BlockingQueue等多种多线程访问共享资源的同步器框架&#xff0c;AQS是一种依赖状态&#xff08;state&#x…

AQS

AQS 简介AQS原理分析AQS原理概览AQS对资源的共享方式AQS低层使用了模板方法模式 AQS组件总结 简介 AQS的全称为&#xff08;AbstractQueuedSynchronizer&#xff09;&#xff0c;这个类在java.util.concurrent.locks包下面。 AQS是一个用来构建锁和同步器的框架&#xff0c;使…

Java技术之AQS详解

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

(面经总结)一篇文章带你完整复习 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;输出…