AQS是什么?
【Java并发】AQS详解:JUC包背后的“幕后大佬”
在 Java 并发编程(JUC)的世界里,我们经常使用 ReentrantLock、CountDownLatch、Semaphore 这些赫赫有名的工具类。
但你是否想过,这些功能各异的工具背后,其实共用着同一套“底盘”?
这就是我们今天要聊的主角——AQS (AbstractQueuedSynchronizer,抽象队列同步器)。它是 JUC 包的心脏,掌握了它,你就掌握了 Java 并发的半壁江山。
一、 什么是 AQS?
AQS 是一个用于构建锁和同步器的框架。
如果把 ReentrantLock、CountDownLatch 等比作是成品的汽车(跑车、卡车、公交车),那么 AQS 就是通用的汽车底盘和引擎。
- AQS 负责脏活累活:它处理了线程的排队、阻塞、唤醒、线程安全等最复杂的底层逻辑。
- 同步器负责业务逻辑:具体的工具类只需要告诉 AQS,“什么时候算获取锁成功”,“资源一共有多少”,剩下的交给 AQS 即可。
简单来说,AQS 是一个“原材料”,我们可以根据它加工出各种各样的同步器。
二、 AQS 的核心架构
AQS 的内部并没有多么神秘,它的核心逻辑主要由三部分组成:
1. 核心资源:State (volatile int)
AQS 内部维护了一个 state 变量,用来表示“同步状态”。
- 这是一个
volatile修饰的int变量,保证了多线程下的可见性。 - AQS 提供了 CAS (Compare And Swap) 方法来原子性地修改这个值。
这个 state 具体代表什么?AQS 不关心,由子类定义:
- 在 ReentrantLock 中,
state=0代表没锁,state=1代表上锁。 - 在 CountDownLatch 中,
state代表倒计时的数值。 - 在 Semaphore 中,
state代表剩余的许可证数量。
2. 等待队列:CLH 队列
如果有线程想抢资源(修改 state)失败了怎么办?总不能让它一直死循环空转(自旋)吧?
AQS 会把这些竞争失败的线程包装成一个 Node 节点,加入到一个双向链表(CLH队列)的尾部,并将线程阻塞(Park)。等待持有资源的线程释放后,再唤醒队列头部的线程。
3. 阻塞与唤醒工具
AQS 底层利用 LockSupport 类中的 park() 和 unpark() 方法,真正实现了操作系统的线程挂起和唤醒。
三、 AQS 是如何工作的?(以抢锁为例)
我们可以把 AQS 的工作流程想象成“去银行柜台办理业务”:
- 尝试获取 (TryAcquire):
- 线程 A 来了,看到柜台窗口(State)是空的(0),立马坐下办理,把窗口状态改为“忙碌”(1)。
- 入队 (Enqueue):
- 线程 B 来了,发现窗口状态是“忙碌”(1)。
- AQS 这位大堂经理就会把线程 B 领到旁边的等待区(CLH 队列),让它排在队伍后面,并告诉它:“你先睡会儿(Block),轮到你了叫你”。
- 释放与唤醒 (Release & Unpark):
- 线程 A 办完业务了,离开柜台,把窗口状态改回“空闲”(0)。
- AQS 大堂经理发现窗口空了,就去等待区叫醒排在最前面的线程 B:“醒醒,轮到你了”。
- 线程 B 醒来,高高兴兴去柜台办理业务。
四、 AQS 的设计模式:模板方法模式
AQS 之所以强大,是因为它使用了模板方法设计模式。
AQS 把“排队”、“阻塞”、“唤醒”这些通用的逻辑都写死了(acquire、release 方法)。但是,“如何判断资源是否足够”、“如何修改 state” 这些逻辑是留给子类去实现的。
作为开发者,我们如果想自定义一个锁,只需要继承 AQS 并重写以下几个“钩子方法”:
tryAcquire(int): 独占方式。尝试获取资源,成功则返回 true,失败则返回 false。tryRelease(int): 独占方式。尝试释放资源。tryAcquireShared(int): 共享方式。尝试获取资源。tryReleaseShared(int): 共享方式。尝试释放资源。isHeldExclusively(): 该线程是否正在独占资源。
五、 AQS 的“成品”展示
让我们看看 Java 大佬们是如何利用 AQS 这个“原材料”加工出著名工具类的:
1. ReentrantLock(可重入锁)
- 模式:独占模式。
- 加工逻辑:
state初始化为 0。- 线程 A 抢锁,通过 CAS 把
state变成 1。 - 如果是同一个线程 A 再次抢锁(重入),就把
state加 1(变成 2, 3…)。 - 释放锁时,
state减 1,直到减为 0 时才真正释放。
2. CountDownLatch(倒计时器)
- 模式:共享模式。
- 加工逻辑:
state初始化为 N(比如 5)。- 主线程调用
await(),如果state > 0,就进入队列等待。 - 其他线程调用
countDown(),通过 CAS 把state减 1。 - 当
state减到 0 时,AQS 唤醒队列里所有等待的主线程。
3. Semaphore(信号量)
- 模式:共享模式。
- 加工逻辑:
state初始化为许可证数量(比如 3)。- 线程来获取许可,
state减 1。 - 如果
state变为负数或者不足,线程进入队列等待。 - 释放许可时,
state加 1。
六、 总结
AQS 是 Java 并发编程的基石。
- 宏观上:它是一个框架,通过State(资源状态)和CLH Queue(等待队列)来管理线程的同步。
- 设计上:它采用了模板方法模式,将复杂的队列管理封装起来,只暴露简单的状态修改接口给子类。
- 应用上:它是
ReentrantLock、Semaphore、CountDownLatch等工具类的父类(的内部实现)。
理解了 AQS,你就理解了 Java 锁机制的灵魂。下次面试官问你:“ReentrantLock 是怎么实现的?”你就可以自信地从 AQS 的 state 和队列讲起了!
AQS的具体原理可以参考: https://javaguide.cn/java/concurrent/aqs.html
希望这篇文章能帮你彻底搞懂 AQS!如果有帮助,欢迎点赞收藏。

