Java的AQS框架设计原理
Java 的 AQS(AbstractQueuedSynchronizer) 是并发包(java.util.concurrent.locks
)的核心框架,为构建锁和同步器提供了底层支持。以下是其核心原理和实现机制的深度解析:
一、AQS 核心设计思想
模板方法模式
AQS 是一个抽象类,定义了同步器的骨架(如获取/释放资源的流程),具体实现由子类完成(如ReentrantLock
、Semaphore
)。CLH 变种队列
通过 双向链表 实现的等待队列(FIFO),存储被阻塞的线程。状态管理
使用volatile int state
表示共享资源状态(如锁的重入次数、信号量的许可数)。
二、核心数据结构
1 | // AQS 内部类 Node 表示等待线程 |
三、核心方法及流程
1. 独占模式(如 ReentrantLock)
获取资源 (
acquire
)1
2
3
4
5public final void acquire(int arg) {
if (!tryAcquire(arg) && // 子类实现尝试获取资源
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 加入队列并阻塞
selfInterrupt();
}tryAcquire(arg)
:子类实现(如ReentrantLock
判断当前线程是否可获取锁)。addWaiter()
:将线程包装为 Node 加入队列尾部。acquireQueued()
:自旋尝试获取资源,失败后调用LockSupport.park()
阻塞。
释放资源 (
release
)1
2
3
4
5
6
7
8
9public final boolean release(int arg) {
if (tryRelease(arg)) { // 子类实现释放资源
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h); // 唤醒后继节点
return true;
}
return false;
}
2. 共享模式(如 Semaphore)
- 获取资源 (
acquireShared
)1
2
3
4public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0) // 子类实现尝试获取共享资源
doAcquireShared(arg); // 加入队列并阻塞
} - 释放资源 (
releaseShared
)1
2
3
4
5
6
7public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) { // 子类实现释放共享资源
doReleaseShared(); // 唤醒后续共享节点
return true;
}
return false;
}
四、关键机制解析
1. 线程阻塞与唤醒
- 阻塞:通过
LockSupport.park()
挂起线程。 - 唤醒:前驱节点释放资源时,调用
unparkSuccessor()
唤醒后继节点。
2. 公平性与非公平性
- 公平锁:严格按队列顺序获取资源(检查队列是否有等待线程)。
- 非公平锁:新线程直接尝试抢锁(
ReentrantLock
默认非公平)。
3. 可重入实现
state
记录重入次数:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// ReentrantLock.Sync 的实现
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) { // 重入逻辑
int nextc = c + acquires;
setState(nextc);
return true;
}
return false;
}
4. 条件变量(Condition)
ConditionObject
:AQS 的内部类,实现条件等待队列。await()
:释放锁并加入条件队列,阻塞当前线程。signal()
:将条件队列中的线程转移到同步队列,等待获取锁。
五、AQS 在 JUC 中的应用
同步器 | AQS 使用方式 |
---|---|
ReentrantLock |
独占模式,state 记录重入次数 |
Semaphore |
共享模式,state 记录剩余许可数 |
CountDownLatch |
共享模式,state 初始为计数 |
ReentrantReadWriteLock |
高16位读锁,低16位写锁 |
六、源码级执行流程(以 ReentrantLock 为例)
- 线程 T1 获取锁:
lock()
→tryAcquire(1)
成功(state=1
),直接获得锁。
- 线程 T2 竞争锁:
tryAcquire(1)
失败 →addWaiter(Node.EXCLUSIVE)
加入队列尾部。acquireQueued()
自旋尝试获取锁,失败后阻塞。
- T1 释放锁:
unlock()
→tryRelease(1)
设置state=0
。unparkSuccessor()
唤醒 T2。
七、AQS 的优劣分析
优势 | 局限性 |
---|---|
灵活支持多种同步器(锁、信号量等) | 需要深入理解才能正确实现子类 |
高性能(CAS + CLH 队列优化) | 不直接支持超时中断以外的复杂条件 |
避免优先级反转(FIFO 队列) | 调试复杂(需分析队列和状态变化) |
八、关键面试问题
为什么 AQS 使用双向链表?
- 便于处理取消节点(
CANCELLED
状态)时快速断开链接。
- 便于处理取消节点(
state
为什么用 volatile?- 保证多线程间的可见性,CAS 操作依赖于此。
如何实现非公平锁?
- 新线程直接尝试 CAS 修改
state
,不检查队列是否有等待线程。
- 新线程直接尝试 CAS 修改
AQS 和 Synchronized 的区别?
- AQS 更灵活(可中断、超时、公平性控制),Synchronized 由 JVM 优化(锁升级)。
通过理解 AQS 的底层机制,可以更高效地使用 JUC 工具,并能自定义符合特定需求的同步器。