吃透 AQS:Java 并发编程的底层基石与实战解析
文章目录
前言
大家好,我是程序员梁白开,今天我们聊一聊AQS(抽象队列同步器)。
在 Java 并发编程领域,有一个 “隐形王者” 始终支撑着 ReentrantLock、CountDownLatch、Semaphore 等核心工具的运行 —— 它就是 AbstractQueuedSynchronizer(简称 AQS)。作为 JUC 包的底层骨架,AQS 通过 “状态控制 + 队列管理” 的巧妙设计,让开发者无需关注复杂的线程排队、阻塞与唤醒逻辑,只需几行代码就能实现自定义同步器。本文将从核心原理、工作机制到实战应用,带你彻底搞懂 AQS 的底层逻辑。
一、AQS 是什么?一句话看透核心定位
AQS 是 Java.util.concurrent.locks 包下的抽象类,本质是构建锁和同步器的通用框架。它的核心设计思想可以概括为两点:
- 状态控制:用 volatile 修饰的 int 变量(state)存储同步状态,支持原子操作
- 队列管理:通过 FIFO 双向队列(CLH 队列变体)管理等待线程
简单说,AQS 就像一个 “并发工具箱”,封装了线程排队、阻塞唤醒等共性逻辑,而将 “是否允许获取资源” 的判断逻辑(如锁的获取条件)留给子类实现。这种设计让 JUC 中的各类同步组件得以快速落地,也让自定义同步器变得简单高效。
二、AQS 核心结构:状态与队列的协作之道
AQS 的核心能力源于两大组件的协同工作:同步状态(state)和同步队列(CLH 队列),再加上条件队列的补充,构成了完整的同步体系。
2.1 同步状态(state):AQS 的 “灵魂变量”
state 是 AQS 中最关键的成员变量,被 volatile 修饰保证可见性,通过 CAS 操作保证原子性修改:
private volatile int state;
// 原子更新状态的核心方法
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
state 的含义由子类自由定义,极具灵活性:
- ReentrantLock 中:state 表示锁的重入次数(0 = 空闲,≥1 = 被占用)
- Semaphore 中:state 表示可用许可数量
- CountDownLatch 中:state 表示倒计时计数器初始值
2.2 同步队列(CLH 队列):线程的 “等候大厅”
当线程尝试获取资源失败时,AQS 会将其封装为 Node 节点,加入一个 FIFO 双向链表(CLH 队列变体)中等待。
2.2.1 Node 节点核心结构
每个节点包含线程引用、等待状态、前后指针等关键信息:
static final class Node {
volatile int waitStatus; // 节点状态(CANCELLED/SIGNAL等)
volatile Node prev; // 前驱节点
volatile Node next; // 后继节点
volatile Thread thread; // 关联线程
Node nextWaiter; // 条件队列后继节点
}
节点状态(waitStatus)的核心取值:
- CANCELLED (1):线程因超时 / 中断取消等待,变为无效节点
- SIGNAL (-1):后继节点需要被唤醒,当前节点释放资源时需触发唤醒
- CONDITION (-2):节点处于条件队列中
- PROPAGATE (-3):共享模式下状态需向后传播
2.2.2 队列的核心特性
- 队列由 head(头节点)和 tail(尾节点)指针维护
- 头节点是 “哨兵节点”,不关联线程,代表当前持有资源的线程
- 新节点通过 CAS 操作加入队尾,保证线程安全
- 双向链表设计让节点取消(如超时)时,能通过 prev/next 指针快速移除,时间复杂度 O (1)(单向链表需 O (n))
2.3 条件队列:线程协作的 “专属通道”
AQS 的内部类 ConditionObject 实现了 Condition 接口,提供 await ()/signal () 机制,对应独立的条件队列:
- 一个 AQS 实例可关联多个条件队列,满足多场景线程协作(如生产者 - 消费者模型)
- 调用 await () 时,线程会释放锁并加入条件队列;调用 signal () 时,节点会被转移到同步队列等待获取锁
- 相比 Object.wait (),ConditionObject 支持精确唤醒、多条件队列,灵活性更高
三、AQS 核心工作机制:两种模式 + 模板方法
AQS 支持两种资源获取模式,通过模板方法模式定义同步逻辑骨架,子类只需重写关键方法即可。
3.1 两种核心模式:独占与共享
独占模式(Exclusive)
- 特性:同一时间仅一个线程能获取资源
- 典型实现:ReentrantLock
- 核心方法:
- acquire (int arg):独占式获取资源,忽略中断
- release (int arg):独占式释放资源,唤醒后继节点
共享模式(Shared)
- 特性:多个线程可同时获取资源
- 典型实现:Semaphore、CountDownLatch
- 核心方法:
- acquireShared (int arg):共享式获取资源,返回≥0 表示成功
- releaseShared (int arg):共享式释放资源,支持 “唤醒传播”(唤醒一个节点后,该节点继续唤醒后续节点)
3.2 模板方法模式:AQS 的设计精髓
AQS 定义了同步操作的骨架(模板方法),子类需重写以下 protected 方法(默认抛出异常):
| 方法 | 功能描述 | 适用模式 |
|---|---|---|
| tryAcquire(int arg) | 独占式获取资源,成功返回 true | 独占 |
| tryRelease(int arg) | 独占式获取资源,成功返回 true | 独占 |
| tryAcquireShared(int arg) | 共享式获取资源,返回≥0 表示成功 | 共享 |
| tryReleaseShared(int arg) | 共享式释放资源,成功返回 true | 共享 |
| isHeldExclusively() | 判断当前线程是否独占资源 | 独占 |
模板方法(如 acquire、release)会调用这些子类实现的方法,完成 “尝试获取 - 排队 - 阻塞 - 唤醒” 的完整流程,极大简化了同步器的实现。
四、高频面试点:AQS 关键问题解析
4.1 公平锁与非公平锁如何实现?
- 非公平锁:线程直接 CAS 抢占资源,无需检查队列(ReentrantLock 默认)
protected final boolean tryAcquire(int acquires) {
if (compareAndSetState(0, 1)) { /* 直接抢锁 */ }
}
- 公平锁:抢占前先调用 hasQueuedPredecessors () 检查队列是否有等待线程
protected final boolean tryAcquire(int acquires) {
if (hasQueuedPredecessors()) return false; // 先查队列
if (compareAndSetState(0, 1)) { /* 抢锁 */ }
}
- 权衡:非公平锁吞吐量高(减少线程切换),但可能导致线程饥饿;公平锁避免饥饿,但上下文切换频繁。
4.2 线程中断如何处理?
- 不可中断模式(acquire ()):线程被中断后继续等待,获取资源后补设中断标记
- 可中断模式(acquireInterruptibly ()):线程被中断时直接抛出 InterruptedException
4.3 ConditionObject 与 Object.wait () 的区别?
| 对比维度 | ConditionObject | Object.wait() |
|---|---|---|
| 绑定对象 | 仅绑定 AQS 实现的锁 | 任意 synchronized 对象 |
| 队列数量 | 支持多个条件队列 | 仅一个等待队列 |
| 唤醒精度 | signal () 精确唤醒指定队列线程 | notify () 随机唤醒一个线程 |
| 中断支持 | 提供 awaitUninterruptibly () | 需手动处理中断 |
五、实战:基于 AQS 实现自定义独占锁
理解 AQS 的最佳方式是动手实践,下面实现一个简单的可重入独占锁:
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Lock;
public class MyReentrantLock implements Lock {
// 内部AQS子类,定义state语义和获取/释放逻辑
private final Sync sync = new Sync();
private static class Sync extends AbstractQueuedSynchronizer {
// 尝试获取独占锁(支持重入)
@Override
protected boolean tryAcquire(int arg) {
Thread current = Thread.currentThread();
int c = getState();
if (c == 0) { // 锁空闲,CAS抢占
if (compareAndSetState(0, arg)) {
setExclusiveOwnerThread(current);
return true;
}
} else if (current == getExclusiveOwnerThread()) { // 重入,递增state
setState(c + arg);
return true;
}
return false; // 其他线程持有锁,获取失败
}
// 尝试释放独占锁
@Override
protected boolean tryRelease(int arg) {
if (Thread.currentThread() != getExclusiveOwnerThread()) {
throw new IllegalMonitorStateException();
}
int c = getState() - arg;
boolean free = (c == 0);
if (free) {
setExclusiveOwnerThread(null); // 完全释放,清空持有线程
}
setState(c);
return free;
}
// 判断当前线程是否独占锁
@Override
protected boolean isHeldExclusively() {
return getExclusiveOwnerThread() == Thread.currentThread();
}
}
// Lock接口实现(调用AQS模板方法)
@Override
public void lock() { sync.acquire(1); }
@Override
public void unlock() { sync.release(1); }
// 其他Lock接口方法(lockInterruptibly、tryLock等)省略...
}
核心逻辑:通过重写 tryAcquire 和 tryRelease 定义 state 的操作规则,AQS 自动处理线程排队、阻塞与唤醒,仅需几十行代码就实现了可重入锁的核心功能。
六、AQS 的应用场景与最佳实践
6.1 适用场景
- 实现复杂同步器:需要自定义锁逻辑(如可中断锁、超时锁、读写锁)
- 资源限流:用 Semaphore 控制并发访问数(如数据库连接池)
- 线程协作:用 CountDownLatch 实现主线程等待多子线程完成
- 读多写少场景:用 ReentrantReadWriteLock 提高并发效率
6.2 不适用场景
- 简单同步需求:直接用 synchronized(JDK1.6 后优化,性能接近 ReentrantLock,无需手动释放)
- 无等待队列需求:用 AtomicInteger 等原子类(基于 CAS,无队列开销)
- 分布式场景:AQS 是进程内同步,跨 JVM 需用 Redis/ZooKeeper 实现分布式锁
总结
AQS 是 Java 并发编程的 “底层密码”,其 “状态控制 + 队列管理” 的设计思想、模板方法模式的应用,让复杂同步逻辑的实现变得标准化。理解 AQS 不仅能看透 JUC 组件的底层原理,更能让你在面对复杂并发场景时,快速构建自定义同步器。
从 ReentrantLock 的公平 / 非公平锁,到 Semaphore 的许可机制,再到 CountDownLatch 的倒计时功能,AQS 的影子无处不在。掌握它,你将在并发编程的世界中更具深度和竞争力。
更多推荐



所有评论(0)