JUC——AQS
AQS(AbstractQueuedSynchronizer,抽象队列同步器) 是 Java 并发包(java.util.concurrent
)中最核心、最底层也最重要的同步框架之一,它是构建锁和同步器(如 ReentrantLock、Semaphore、CountDownLatch、ReentrantReadWriteLock 等)的基石。
一、AQS 是什么?
AQS(AbstractQueuedSynchronizer) 是 Java 并发包中的一个抽象类,位于:
java.util.concurrent.locks.AbstractQueuedSynchronizer
它为构建锁和同步器提供了一个灵活的、可重用的底层框架,通过它可以非常方便地实现:
- 独占式获取与释放锁(如 ReentrantLock)
- 共享式获取与释放资源(如 Semaphore、CountDownLatch、ReadWriteLock)
🎯 你可以把 AQS 理解为:构建锁和同步器的“骨架”或“基础设施”,它处理了线程排队、阻塞/唤醒、状态管理等复杂逻辑,开发者只需要专注实现“资源是否可被获取”的逻辑即可。
二、AQS 的核心思想
AQS 的核心是围绕一个“状态”(state)变量,并通过一个FIFO 等待队列(CLH 队列的变种)来管理获取资源失败的线程。
核心组成:
组件 | 说明 |
---|---|
state(同步状态) | 一个 |
CLH 队列(线程等待队列) | 一个 FIFO 的线程等待队列,用于存放那些获取资源失败的线程。是 AQS 实现线程排队和阻塞/唤醒的核心。 |
模板方法模式 | AQS 采用模板方法设计模式,它定义了一系列protected 方法(如 tryAcquire、tryRelease)让子类去实现,而它自己实现了线程排队、阻塞、唤醒等通用逻辑。 |
三、AQS 的两种同步模式
AQS 支持两种资源访问模式:
1. 独占模式(Exclusive)
- 同一时刻只有一个线程能获取到资源(如 ReentrantLock)。
- 方法:
tryAcquire(int)
:尝试获取资源,成功返回 true,失败返回 false。tryRelease(int)
:尝试释放资源。- 对应的排队与阻塞机制由 AQS 内部实现。
代表实现类:ReentrantLock
2. 共享模式(Shared)
- 同一时刻可以有多个线程同时获取资源(如 Semaphore、CountDownLatch)。
- 方法:
tryAcquireShared(int)
:尝试获取共享资源。tryReleaseShared(int)
:尝试释放共享资源。- AQS 会控制并发线程数或资源数。
代表实现类:Semaphore、CountDownLatch、ReentrantReadWriteLock(读锁部分)
四、AQS 的核心方法(分为两类)
AQS 的设计采用了模板方法模式,它已经实现好了大部分通用逻辑(如线程排队、阻塞/唤醒),但关键的资源是否可获取/释放的逻辑,交由子类去实现。
1. 需要子类实现的方法(核心抽象/protected 方法)
这些方法是 AQS 框架要求子类去实现的,用于定义“如何获取/释放资源”。
独占模式相关方法:
方法 | 说明 |
---|---|
| 尝试以独占方式获取资源,成功返回 true,否则 false |
| 尝试以独占方式释放资源 |
| 当前线程是否独占资源(用于判断是否是锁的持有者) |
共享模式相关方法:
方法 | 说明 |
---|---|
| 尝试以共享方式获取资源,返回剩余资源数,负数表示失败 |
| 尝试以共享方式释放资源 |
| 是否被当前线程独占(某些共享锁也需要) |
2. AQS 已经实现的方法(通用逻辑,开发者一般不重写)
这些方法由 AQS 自己实现,用于管理线程的排队、阻塞、唤醒等操作,包括:
acquire(int arg)
:独占式获取资源,如果获取不到就进入等待队列并可能阻塞。release(int arg)
:独占式释放资源,并唤醒后续等待线程。acquireShared(int arg)
:共享式获取资源。releaseShared(int arg)
:共享式释放资源。hasQueuedThreads()
:是否有线程在等待。getQueueLength()
:获取等待队列长度。- ……
五、AQS 的内部实现原理(简化版)
1. 核心状态:state
- 是一个
volatile int
变量,表示当前同步状态。 - 比如:
- 在 ReentrantLock 中,state = 0 表示锁未被占用,=1 表示被占用,>1 表示重入次数。
- 在 Semaphore 中,state 表示剩余的许可数量。
2. CLH 队列(线程等待队列)
- AQS 内部维护了一个 FIFO 的线程等待队列,当某个线程获取资源失败时,会被封装为一个 Node 节点加入队列尾部,并可能被挂起(阻塞)。
- 当资源被释放时,AQS 会从队列头部唤醒一个或多个等待线程。
CLH 队列是 Craig、Landin 和 Hagersten 三人提出的一种基于链表的可扩展、高性能、公平的自旋锁队列,AQS 借鉴并优化了它的思想。
3. Node 节点
每个等待线程会被封装成一个 Node 对象,包含:
- 线程引用(thread)
- 等待状态(waitStatus)
- 前驱/后继节点引用(prev / next)
- 是独占模式还是共享模式
六、AQS 的使用:以 ReentrantLock 为例
虽然我们一般不直接使用 AQS,但了解它有助于理解 Java 中各种锁和同步器的工作原理。
ReentrantLock 的底层就是基于 AQS 实现的独占锁:
- ReentrantLock 内部有一个 Sync 类(继承 AQS)
- Sync 又分为:
- NonfairSync(非公平锁)
- FairSync(公平锁)
ReentrantLock 通过调用 AQS 的:
acquire(1)
→ 尝试获取锁,失败进入队列release(1)
→ 释放锁,并唤醒其他线程
而这些方法的内部逻辑,AQS 已经帮我们实现了,我们只需要通过继承 AQS 并实现 tryAcquire()
和 tryRelease()
方法,就能定制自己的锁!
七、如何自己基于 AQS 实现一个简单的同步器?
虽然很少直接使用 AQS,但理解它后,我们可以尝试实现一个简单的“自定义锁”或信号量。
简单示例:实现一个不可重入的独占锁(简化版)
class MyLock {private static class Sync extends AbstractQueuedSynchronizer {// 尝试获取锁@Overrideprotected boolean tryAcquire(int acquires) {if (compareAndSetState(0, 1)) { // state == 0 表示未被占用setExclusiveOwnerThread(Thread.currentThread()); // 设置当前线程为锁持有者return true;}return false;}// 尝试释放锁@Overrideprotected boolean tryRelease(int releases) {if (getState() == 0) throw new IllegalMonitorStateException();setExclusiveOwnerThread(null);setState(0);return true;}// 是否被当前线程独占@Overrideprotected boolean isHeldExclusively() {return getState() == 1 && getExclusiveOwnerThread() == Thread.currentThread();}}private final Sync sync = new Sync();public void lock() {sync.acquire(1);}public void unlock() {sync.release(1);}
}
八、总结:AQS 核心概念一句话
AQS(AbstractQueuedSynchronizer)是 Java 并发包中用于构建锁和同步器的核心框架,它通过一个 volatile 状态变量和 FIFO 等待队列,提供了线程同步的底层支持,开发者可通过继承 AQS 并实现 tryAcquire / tryRelease 等方法,轻松实现自定义的同步组件。