1. AQS核心概念与设计哲学
在Java并发编程领域,AbstractQueuedSynchronizer(AQS)堪称并发控制的基石。作为JDK并发包的核心框架,它采用模板方法设计模式,将线程同步的通用逻辑抽象出来,让开发者能够轻松实现各种自定义同步器。
1.1 AQS的四大核心组件
AQS的精妙之处在于它用四个简单而强大的组件构建了整个同步体系:
-
volatile int state:同步状态的核心变量,不同的锁实现对其有不同解读。在ReentrantLock中表示锁的持有计数,在Semaphore中则表示可用许可数量。
-
CLH队列:一个虚拟的双向链表队列(实际是变种的CLH锁),用于管理获取同步状态失败的线程。这个队列采用FIFO原则,保证公平性。
-
Condition队列:与Object的wait/notify机制类似,但更灵活的条件等待队列。每个Condition对象都维护一个独立的条件队列。
-
Node节点:封装了线程及其等待状态的数据结构,是上述两个队列的基本组成单元。Node中waitStatus的不同值代表了线程的不同等待状态。
关键理解:AQS将复杂的线程同步问题简化为对state变量的操作和队列管理。这种抽象使得各种同步工具的实现变得异常简洁。
1.2 AQS的两种工作模式
AQS支持两种截然不同的同步模式,几乎涵盖了所有并发控制场景:
独占模式(Exclusive):
- 典型实现:ReentrantLock
- 特点:同一时刻只有一个线程能获取同步状态
- 应用场景:写操作、互斥访问等需要严格串行化的场景
共享模式(Shared):
- 典型实现:Semaphore、CountDownLatch
- 特点:允许多个线程同时获取同步状态
- 应用场景:读操作、资源池等允许并行访问的场景
模式选择对比表:
| 特性 | 独占模式 | 共享模式 |
|---|---|---|
| 线程数量 | 单线程 | 多线程 |
| 典型应用 | ReentrantLock | Semaphore |
| state含义 | 持有计数 | 可用数量 |
| 唤醒策略 | 精确唤醒一个 | 传播唤醒多个 |
| 重写方法 | tryAcquire/tryRelease | tryAcquireShared/tryReleaseShared |
2. AQS深度解析:数据结构与实现原理
2.1 Node节点的精妙设计
Node是AQS的核心数据结构,每个等待线程都被封装为一个Node节点。其设计亮点包括:
java复制static final class Node {
// 等待状态
volatile int waitStatus;
// 同步队列指针
volatile Node prev;
volatile Node next;
// 条件队列指针
Node nextWaiter;
// 绑定的线程
volatile Thread thread;
// 等待状态常量
static final int CANCELLED = 1; // 取消状态
static final int SIGNAL = -1; // 需要唤醒后继
static final int CONDITION = -2; // 在条件队列等待
static final int PROPAGATE = -3; // 共享模式下传播唤醒
}
waitStatus的四种关键状态:
- SIGNAL:当前节点的后继节点需要被唤醒。这是同步队列中最常见的状态。
- CANCELLED:表示线程已取消等待,通常由于超时或中断。
- CONDITION:表示节点在条件队列中等待。
- PROPAGATE:仅在共享模式下使用,表示唤醒需要传播。
2.2 CLH同步队列的运作机制
CLH队列是AQS实现线程排队等待的核心数据结构,其运作特点包括:
-
队列结构:
- 双向链表(实际是CLH锁的变种)
- 头节点是虚节点(dummy node),不关联具体线程
- 新节点总是追加到尾节点之后
-
入队流程:
- 线程获取锁失败时,会创建Node节点
- 通过CAS操作将节点安全地添加到队列尾部
- 如果队列为空,会先初始化一个虚节点作为头节点
-
出队流程:
- 头节点释放锁后,会唤醒其后继节点
- 被唤醒的节点成为新的头节点
- 原头节点从队列中断开
技术细节:AQS的CLH队列实际上是原始CLH锁的变种。原始CLH锁是单向链表且节点自旋,而AQS改为双向链表并结合了阻塞机制。
2.3 Condition队列的工作原理
Condition队列为线程提供了更灵活的等待/通知机制:
-
队列结构:
- 单向链表(仅通过nextWaiter连接)
- 每个Condition对象维护独立的队列
- 节点状态固定为CONDITION
-
await()流程:
- 释放持有的锁
- 创建CONDITION节点加入条件队列
- 完全阻塞直到被signal或中断
-
signal()流程:
- 将条件队列的头节点转移到同步队列
- 转移后的节点状态从CONDITION变为0
- 节点在同步队列中等待重新获取锁
条件队列与同步队列的关系图示:
code复制同步队列: head ↔ node1 ↔ node2 ↔ tail
条件队列: firstWaiter → nodeA → nodeB → lastWaiter
3. AQS的核心工作流程
3.1 独占模式实现原理
3.1.1 加锁流程详解
独占锁的获取过程是AQS最复杂的部分之一,其核心方法acquire的代码如下:
java复制public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
加锁的五个关键阶段:
-
快速尝试:首先调用tryAcquire尝试直接获取锁,这是留给子类实现的模板方法。
-
创建节点:如果快速尝试失败,调用addWaiter创建独占模式的Node节点。
-
入队操作:通过CAS操作将新节点安全地添加到队列尾部。
-
队列中等待:acquireQueued方法中,节点会自旋尝试获取锁,失败后可能进入阻塞状态。
-
中断处理:如果在等待过程中被中断,会在获取锁后补上中断标志。
acquireQueued的核心逻辑:
java复制final boolean acquireQueued(final Node node, int arg) {
boolean interrupted = false;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node))
interrupted |= parkAndCheckInterrupt();
}
} catch (Throwable t) {
cancelAcquire(node);
throw t;
}
}
3.1.2 解锁流程解析
独占锁的释放相对简单,主要包含两个关键步骤:
java复制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尝试释放同步状态,这是子类需要实现的方法。
-
后继唤醒:如果释放成功且头节点状态不为0(表示有后继节点需要唤醒),则调用unparkSuccessor唤醒后继节点。
unparkSuccessor的优化策略:
- 从尾向前查找可唤醒的节点(处理并发取消的情况)
- 跳过已取消的节点(waitStatus > 0)
- 使用LockSupport.unpark精确唤醒线程
3.2 共享模式实现原理
3.2.1 共享锁获取流程
共享模式的获取流程与独占模式类似,但增加了传播唤醒的特性:
java复制public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
关键区别点:
- tryAcquireShared返回int值,表示剩余资源量
- 获取成功后调用setHeadAndPropagate传播唤醒
- 释放时可能连续唤醒多个等待线程
传播唤醒的核心代码:
java复制private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
setHead(node);
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared();
}
}
3.2.2 共享锁释放流程
共享锁的释放需要考虑唤醒传播的问题:
java复制public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
doReleaseShared的核心逻辑:
- 循环检查头节点状态
- 如果头节点状态为SIGNAL,则尝试唤醒后继
- 唤醒后新的头节点可能继续传播唤醒
- 使用CAS修改头节点状态保证线程安全
4. AQS的高级特性与实现技巧
4.1 模板方法设计模式的应用
AQS是模板方法模式的经典实现,它定义了同步器的骨架,而将具体状态获取/释放的逻辑交给子类实现。这种设计带来了极大的灵活性:
需要子类实现的关键方法:
tryAcquire:尝试获取独占锁tryRelease:尝试释放独占锁tryAcquireShared:尝试获取共享锁tryReleaseShared:尝试释放共享锁isHeldExclusively:查询是否独占持有
模板方法的优势:
- 将复杂的同步逻辑封装在基类中
- 子类只需关注状态管理的核心逻辑
- 保证了同步行为的正确性和一致性
- 大大减少了实现自定义同步器的工作量
4.2 中断与超时处理
AQS提供了完善的中断和超时机制,这是比synchronized更灵活的地方:
中断处理策略:
- 独占模式下,acquireInterruptibly会响应中断
- 共享模式下,acquireSharedInterruptibly同样支持中断
- 被中断的线程会抛出InterruptedException
超时控制实现:
java复制public final boolean tryAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
return tryAcquire(arg) ||
doAcquireNanos(arg, nanosTimeout);
}
doAcquireNanos的关键点:
- 使用System.nanoTime计算剩余时间
- 自旋尝试获取锁以减少不必要的park
- 精确控制park时间
- 超时后取消节点并从队列中移除
4.3 公平性与非公平性实现
AQS本身不强制公平性,但基于AQS实现的锁可以灵活选择公平策略:
公平锁实现要点:
- tryAcquire先检查是否有等待线程
- 如果有则直接返回获取失败
- 确保严格按照CLH队列顺序获取锁
非公平锁实现要点:
- 直接尝试CAS获取锁
- 失败后再进入队列排队
- 新来的线程可能"插队"成功
公平性对比实验数据(仅供参考):
| 场景 | 公平锁吞吐量 | 非公平锁吞吐量 |
|---|---|---|
| 低竞争 | 1000 ops/ms | 1500 ops/ms |
| 高竞争 | 500 ops/ms | 1200 ops/ms |
实际选择:在锁持有时间较短的场景,非公平锁通常能提供更好的吞吐量;而在需要严格顺序保证的场景,公平锁更为合适。
5. AQS在JDK中的典型应用
5.1 ReentrantLock的实现剖析
ReentrantLock是AQS最经典的独占模式实现:
核心实现类:
java复制abstract static class Sync extends AbstractQueuedSynchronizer {
// 非公平尝试获取
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
// 尝试释放
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
}
关键特性:
- 支持可重入:同一个线程可以多次获取锁
- 支持公平/非公平两种模式选择
- 提供Condition的灵活支持
- 完善的锁状态查询方法
5.2 Semaphore的工作原理
Semaphore是AQS共享模式的典型代表:
核心实现逻辑:
java复制protected int tryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
protected boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases;
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
return true;
}
}
设计亮点:
- 使用state表示可用许可数量
- tryAcquireShared返回剩余许可数
- 支持公平/非公平两种获取方式
- 释放许可时可能连续唤醒多个等待线程
5.3 CountDownLatch的巧妙设计
CountDownLatch是典型的"一次性"同步工具:
核心实现:
java复制protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
使用场景:
- 主线程等待多个工作线程完成
- 多个线程等待某个初始化操作完成
- 模拟并发测试场景
- 作为简单的任务完成通知机制
6. AQS的最佳实践与性能优化
6.1 实现自定义同步器
基于AQS实现自定义同步器通常只需要以下几个步骤:
- 定义继承AQS的内部类
- 根据需求重写tryAcquire/tryRelease或共享模式方法
- 提供对外的同步操作方法
- 可选实现ConditionObject支持条件等待
示例:简单的互斥锁实现:
java复制class Mutex {
private static class Sync extends AbstractQueuedSynchronizer {
protected boolean tryAcquire(int acquires) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
protected boolean tryRelease(int releases) {
if (getState() == 0) throw new IllegalMonitorStateException();
setExclusiveOwnerThread(null);
setState(0);
return true;
}
}
private final Sync sync = new Sync();
public void lock() { sync.acquire(1); }
public void unlock() { sync.release(1); }
}
6.2 性能调优经验
在实际使用AQS及其衍生工具时,有几个关键的性能考量点:
-
减少锁竞争:
- 缩小临界区范围
- 使用读写锁分离读/写操作
- 考虑使用乐观锁或CAS操作
-
合理选择公平性:
- 非公平锁通常吞吐量更高
- 公平锁能减少线程饥饿
- 根据实际场景权衡选择
-
避免过度阻塞:
- 优先使用tryLock尝试获取锁
- 设置合理的超时时间
- 考虑使用条件变量减少无效等待
-
监控与诊断:
- 使用JVM工具监控锁竞争情况
- 关注AQS队列长度指标
- 合理设置线程dump分析策略
6.3 常见问题排查
在使用AQS相关工具时,可能会遇到以下典型问题:
问题1:死锁
- 症状:线程阻塞且不释放锁
- 排查:使用jstack分析线程栈
- 预防:按固定顺序获取锁,使用tryLock
问题2:线程饥饿
- 症状:某些线程长期无法获取锁
- 排查:检查锁的公平性设置
- 解决:考虑使用公平锁或调整业务逻辑
问题3:性能瓶颈
- 症状:系统吞吐量下降,CPU利用率不高
- 排查:使用profiler工具分析锁竞争
- 优化:减少锁粒度,使用读写锁
问题4:虚假唤醒
- 症状:线程意外从等待中唤醒
- 防护:始终在循环中检查条件
- 最佳实践:使用标准的await模式
java复制// 正确的条件等待模式
while (!condition) {
condition.await();
}
7. AQS的底层机制与并发原理
7.1 CAS操作的核心作用
AQS的实现大量依赖CAS(Compare-And-Swap)操作,这是现代并发编程的基石:
AQS中使用CAS的关键场景:
- state变量的修改
- CLH队列的节点插入
- 节点状态的变更
- 头尾指针的更新
CAS的优势:
- 无锁操作,减少线程阻塞
- 原子性保证,避免竞态条件
- 硬件级别支持,性能高效
Java中的CAS实现:
java复制// Unsafe类中的CAS方法
public final native boolean compareAndSwapInt(
Object o, long offset, int expected, int x);
7.2 内存屏障与可见性保证
AQS通过volatile变量和内存屏障保证多线程环境下的可见性:
关键设计:
- state变量声明为volatile
- head/tail节点指针也是volatile
- 在关键操作前后插入内存屏障
happens-before关系:
- state的修改对其他线程立即可见
- 节点入队操作的有序性保证
- 锁释放与获取之间的顺序保证
7.3 线程阻塞与唤醒机制
AQS使用LockSupport工具进行线程阻塞和唤醒:
与Object监视器方法的对比:
| 特性 | LockSupport | Object监视器 |
|---|---|---|
| 前置条件 | 无 | 必须持有对象锁 |
| 精确唤醒 | 支持 | 不支持 |
| 唤醒顺序 | 与park顺序无关 | FIFO顺序 |
| 灵活性 | 更高 | 较低 |
| 性能 | 更优 | 稍差 |
LockSupport的核心方法:
park():阻塞当前线程unpark(Thread):唤醒指定线程parkNanos(long):带超时的阻塞
实现细节:LockSupport底层使用per-thread的许可标志,unpark先于park调用也不会导致线程永久阻塞。
8. AQS的演进与替代方案
8.1 Java并发包的演进
随着Java版本更新,并发工具也在不断发展:
- Java 5:引入AQS和JUC包
- Java 6:优化锁性能,改进CLH队列
- Java 7:新增ForkJoinPool
- Java 8:引入CompletableFuture
- Java 9:增强并发工具类
8.2 其他并发控制方案
虽然AQS功能强大,但在某些场景下可能有更好的选择:
-
synchronized:
- JVM持续优化后性能已接近AQS
- 语法更简洁
- 适合简单的同步需求
-
StampedLock:
- 提供乐观读模式
- 适合读多写少场景
- 避免写线程饥饿
-
VarHandle:
- Java 9引入
- 提供更细粒度的内存访问控制
- 适合实现自定义并发结构
-
并发数据结构:
- ConcurrentHashMap
- CopyOnWriteArrayList
- 优先考虑使用这些线程安全集合
8.3 AQS的设计启示
AQS的设计给我们提供了许多有价值的启示:
- 模板方法模式的强大应用
- 关注点分离的优秀实践
- 无锁编程的性能优势
- 队列管理的精妙设计
- 可重入的灵活实现
- 条件变量的独立支持
这些设计思想不仅适用于同步器实现,也可以借鉴到其他复杂系统的设计中。