一句话总结
AQS(AbstractQueuedSynchronizer)是 JUC 包的核心框架,ReentrantLock、CountDownLatch、Semaphore 等都基于它实现。核心思想:用一个 volatile int state 表示同步状态,用一个 FIFO 双向队列(CLH 变体)管理等待线程。提供独占模式(tryAcquire/tryRelease)和共享模式(tryAcquireShared/tryReleaseShared),子类只需重写这些模板方法即可实现自定义同步器。
初级理解
AQS 是什么?
AQS 全称 AbstractQueuedSynchronizer,是一个抽象类,提供了构建锁和同步器的框架。它封装了线程排队、阻塞、唤醒等底层操作,子类只需实现获取/释放的逻辑。
基于 AQS 的常见类
| 类 | AQS 模式 | state 含义 |
| ReentrantLock | 独占 | 0=未锁,1=已锁,>1=重入次数 |
| CountDownLatch | 共享 | 倒计数值,0 时释放所有等待线程 |
| Semaphore | 共享 | 剩余许可数 |
| ReentrantReadWriteLock | 独占+共享 | 高16位=读锁次数,低16位=写锁次数 |
AQS 核心三要素
public abstract class AbstractQueuedSynchronizer {
// ① volatile 的同步状态
private volatile int state;
// ② FIFO 等待队列的头尾指针
private transient volatile Node head;
private transient volatile Node tail;
// ③ 队列节点
static final class Node {
volatile Node prev; // 前驱节点
volatile Node next; // 后继节点
volatile Thread thread; // 等待的线程
volatile int waitStatus; // 等待状态
Node nextWaiter; // 条件队列的下一个节点
}
}
一句话总结:AQS = volatile state + CLH 队列 + 模板方法模式,是 JUC 的基石。
中级深入
独占模式 — acquire/release 流程
以 ReentrantLock 为例,分析独占锁的获取和释放流程:
// AQS.acquire() 源码(简化)
public final void acquire(int arg) {
// ① tryAcquire:子类实现,尝试获取锁
// ② addWaiter:获取失败,将当前线程包装成 Node 加入队列
// ③ acquireQueued:在队列中自旋等待
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
// addWaiter:将线程加入等待队列
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); // CAS 失败则自旋入队
return node;
}
// acquireQueued:在队列中自旋等待
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
// 如果前驱是 head,尝试获取锁
if (p == head && tryAcquire(arg)) {
setHead(node); // 获取成功,设为新 head
p.next = null;
failed = false;
return false;
}
// 判断是否需要阻塞
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt()) // LockSupport.park() 阻塞
return true;
}
} finally {
if (failed) cancelAcquire(node);
}
}
释放流程
// AQS.release() 源码(简化)
public final boolean release(int arg) {
if (tryRelease(arg)) { // 子类实现,释放锁
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h); // 唤醒 head 的下一个节点
return true;
}
return false;
}
// unparkSuccessor:唤醒后继节点
private void unparkSuccessor(Node node) {
Node s = node.next;
if (s == null || s.waitStatus > 0) {
// 从 tail 往前找第一个需要唤醒的节点
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0) s = t;
}
if (s != null)
LockSupport.unpark(s.thread); // 唤醒线程
}
共享模式 — acquireShared/releaseShared
共享模式与独占模式的区别:多个线程可以同时获取锁。以 CountDownLatch 为例:
// CountDownLatch 的 tryAcquireShared
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1; // state=0 时获取成功
}
// CountDownLatch 的 tryReleaseShared
protected boolean tryReleaseShared(int releases) {
for (;;) {
int c = getState();
if (c == 0) return false;
int nextc = c - 1;
if (compareAndSetState(c, nextc))
return nextc == 0; // state 减到 0 时释放所有等待线程
}
}
中级要点:acquire 失败后线程包装成 Node 入队,前驱是 head 时自旋尝试获取,否则 park 阻塞。release 时唤醒 head 的下一个节点。
高级拓展
CLH 队列变体 — 为什么从 tail 往前找?
AQS 使用的是 CLH 锁队列的变体。标准 CLH 队列中,每个节点自旋等待前驱节点释放锁。AQS 改进了这一点:节点在队列中阻塞(park)而非自旋,由前驱节点释放时唤醒(unpark)。
在 unparkSuccessor 中,为什么从 tail 往前找而不是从 head 往后找?因为入队时先设置 prev 再 CAS 设置 tail,最后设置 next。如果从 head 往后找,可能因为 next 还没设置好而漏掉节点。
ReentrantLock 的公平锁 vs 非公平锁
// 非公平锁的 tryAcquire
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 直接 CAS 抢锁,不管队列里有没有等待线程
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
// 重入
int nextc = c + acquires;
setState(nextc);
return true;
}
return false;
}
// 公平锁的 tryAcquire
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 先检查队列里有没有等待线程,有则不抢
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// ... 重入逻辑相同
}
Condition 条件队列
AQS 内部还有一个条件队列(单向链表),用于实现 Condition.await()/signal()。调用 await() 时线程从同步队列移到条件队列,调用 signal() 时从条件队列移回同步队列。
// Condition.await() 简化流程
public final void await() throws InterruptedException {
Node node = addConditionWaiter(); // 加入条件队列
int savedState = fullyRelease(node); // 释放锁
while (!isOnSyncQueue(node)) {
LockSupport.park(this); // 阻塞等待 signal
}
acquireQueued(node, savedState); // 被唤醒后重新竞争锁
}
// Condition.signal() 简化流程
public final void signal() {
Node first = firstWaiter;
if (first != null)
doSignal(first); // 将节点从条件队列移到同步队列
}
高级加分项:能解释 AQS 为什么从 tail 往前找后继节点、公平锁通过 hasQueuedPredecessors 判断、Condition 的条件队列与同步队列的转换关系。
实战场景
场景一:用 AQS 实现简单的互斥锁
public class SimpleLock {
private final Sync sync = new Sync();
private static class Sync extends AbstractQueuedSynchronizer {
protected boolean tryAcquire(int arg) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
protected boolean tryRelease(int arg) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
}
public void lock() { sync.acquire(1); }
public void unlock() { sync.release(1); }
}
场景二:用 AQS 实现限流器
public class RateLimiter {
private final Sync sync;
public RateLimiter(int permits) {
sync = new Sync(permits);
}
public void acquire() { sync.acquireShared(1); }
public void release() { sync.releaseShared(1); }
private static class Sync extends AbstractQueuedSynchronizer {
Sync(int permits) { setState(permits); }
protected int tryAcquireShared(int arg) {
for (;;) {
int current = getState();
if (current <= 0) return -1;
if (compareAndSetState(current, current - 1))
return 1;
}
}
protected boolean tryReleaseShared(int arg) {
for (;;) {
int current = getState();
if (compareAndSetState(current, current + 1))
return true;
}
}
}
}
面试模拟
Q:AQS 的原理是什么?
A:AQS 核心是 volatile int state + CLH 变体队列 + 模板方法模式。state 表示同步状态,队列管理等待线程。提供独占(acquire/release)和共享(acquireShared/releaseShared)两种模式。子类重写 tryAcquire/tryRelease 等方法实现具体逻辑。获取失败时线程包装成 Node 入队并 park 阻塞,释放时 unpark 唤醒后继节点。
Q:AQS 的公平锁和非公平锁怎么实现的?
A:区别在 tryAcquire 方法。非公平锁在 state=0 时直接 CAS 抢锁,不管队列里有没有等待线程;公平锁会先调用 hasQueuedPredecessors() 检查队列中是否有等待更久的线程,有则不抢。非公平锁性能更好(减少线程切换),公平锁避免饥饿。
Q:AQS 中为什么从 tail 往前找后继节点?
A:因为入队操作分三步:设置 prev → CAS 设置 tail → 设置前驱的 next。这三步不是原子的,如果从 head 往后找,可能因为前驱的 next 还没设置而漏掉刚入队的节点。从 tail 往前找通过 prev 指针,prev 在 CAS 之前就设置好了,更可靠。