AQS原理剖析
深度剖析 AQS(AbstractQueuedSynchronizer)核心原理
AQS(AbstractQueuedSynchronizer)是 Java 并发包中最重要的基础组件之一,它是构建锁和其他同步工具的核心框架。ReentrantLock
、Semaphore
、CountDownLatch
等工具都是基于 AQS 实现的。下面我们将从基础概念、核心数据结构、源码剖析等方面,层层递进地深入讲解 AQS 的原理。
1. AQS 的核心思想
AQS 的核心思想是:
- 通过一个共享的 state 变量来表示同步状态。
- 通过一个 FIFO 队列(CLH 队列)来管理等待线程。
- 通过 CAS 操作来实现线程安全的 state 更新。
AQS 的设计采用了模板方法模式,开发者只需要实现 tryAcquire
、tryRelease
等方法,AQS 会自动处理线程的排队和唤醒。
2. AQS 的核心数据结构
2.1 同步状态(state)
- state:一个
volatile
修饰的int
变量,表示同步状态。不同的同步工具对 state 的解释不同:ReentrantLock
:state 表示锁的重入次数。Semaphore
:state 表示剩余的许可数。CountDownLatch
:state 表示剩余的计数。
2.2 CLH 队列
- CLH 队列:一个双向链表,用于管理等待线程。每个节点(
Node
)代表一个等待线程。 - Node 结构:
static final class Node { volatile int waitStatus; // 等待状态 volatile Node prev; // 前驱节点 volatile Node next; // 后继节点 volatile Thread thread; // 等待线程 Node nextWaiter; // 条件队列的后继节点 }
waitStatus
:表示节点的状态,如CANCELLED
(取消)、SIGNAL
(需要唤醒后继节点)等。prev
和next
:用于构建双向链表。thread
:等待的线程。
3. AQS 的核心方法
AQS 的核心方法是 acquire
和 release
,它们分别用于获取和释放同步状态。
3.1 acquire
方法
acquire
方法用于获取同步状态,如果获取失败,则线程进入等待队列。
public final void acquire(int arg) {
if (!tryAcquire(arg) && // 尝试获取同步状态
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 加入等待队列并自旋
selfInterrupt(); // 如果线程在等待过程中被中断,则恢复中断状态
}
tryAcquire
:由子类实现,尝试获取同步状态。addWaiter
:将当前线程包装成Node
并加入等待队列。acquireQueued
:线程在队列中自旋,直到获取同步状态。
3.2 release
方法
release
方法用于释放同步状态,并唤醒后继节点。
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
:由子类实现,尝试释放同步状态。unparkSuccessor
:唤醒后继节点的线程。
4. AQS 的源码剖析
4.1 addWaiter
方法
addWaiter
方法将当前线程包装成 Node
并加入等待队列。
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode); // 创建节点
Node pred = tail;
if (pred != null) { // 如果队列不为空,尝试快速插入
node.prev = pred;
if (compareAndSetTail(pred, node)) { // CAS 更新尾节点
pred.next = node;
return node;
}
}
enq(node); // 如果快速插入失败,则进入完整入队流程
return node;
}
compareAndSetTail
:CAS 操作,确保线程安全地更新尾节点。enq
:完整入队流程,确保节点成功加入队列。
4.2 acquireQueued
方法
acquireQueued
方法让线程在队列中自旋,直到获取同步状态。
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor(); // 获取前驱节点
if (p == head && tryAcquire(arg)) { // 如果前驱是头节点且成功获取同步状态
setHead(node); // 将当前节点设为头节点
p.next = null; // 断开旧头节点
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) && // 检查是否需要阻塞
parkAndCheckInterrupt()) // 阻塞线程并检查中断状态
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node); // 如果失败,则取消获取
}
}
shouldParkAfterFailedAcquire
:检查是否需要阻塞线程。parkAndCheckInterrupt
:阻塞线程并检查中断状态。
4.3 unparkSuccessor
方法
unparkSuccessor
方法唤醒后继节点的线程。
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0); // 清除状态
Node s = node.next;
if (s == null || s.waitStatus > 0) { // 如果后继节点无效,则从尾节点开始查找
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread); // 唤醒线程
}
compareAndSetWaitStatus
:CAS 操作,清除节点状态。LockSupport.unpark
:唤醒线程。
5. AQS 的应用
5.1 ReentrantLock
的实现
ReentrantLock
是基于 AQS 实现的独占锁。它的 tryAcquire
和 tryRelease
方法如下:
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) { // 如果锁未被占用
if (compareAndSetState(0, acquires)) { // CAS 获取锁
setExclusiveOwnerThread(current); // 设置独占线程
return true;
}
} else if (current == getExclusiveOwnerThread()) { // 如果锁已被当前线程占用
int nextc = c + acquires; // 重入次数加 1
if (nextc < 0) // 溢出检查
throw new Error("Maximum lock count exceeded");
setState(nextc); // 更新 state
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
int c = getState() - releases; // 重入次数减 1
if (Thread.currentThread() != getExclusiveOwnerThread()) // 检查当前线程是否持有锁
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) { // 如果锁完全释放
free = true;
setExclusiveOwnerThread(null); // 清除独占线程
}
setState(c); // 更新 state
return free;
}
5.2 Semaphore
的实现
Semaphore
是基于 AQS 实现的共享锁。它的 tryAcquireShared
和 tryReleaseShared
方法如下:
protected int tryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 || compareAndSetState(available, remaining)) // CAS 更新 state
return remaining;
}
}
protected boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases;
if (next < current) // 溢出检查
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next)) // CAS 更新 state
return true;
}
}
6. 总结
AQS 的核心原理可以总结为以下几点:
- 同步状态:通过
state
变量表示同步状态。 - 等待队列:通过 CLH 队列管理等待线程。
- CAS 操作:通过 CAS 实现线程安全的 state 更新。
- 模板方法:子类实现
tryAcquire
和tryRelease
等方法,AQS 负责线程排队和唤醒。
通过深入理解 AQS 的原理,可以更好地掌握 Java 并发工具的实现机制,并能够灵活地实现自定义的同步工具。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)