AQS 的原理是什么?

2025年 阅读约 12 分钟 面试指南 · Java面试 · Java并发

深入解析AQS(AbstractQueuedSynchronizer)核心原理:CLH变体队列、state状态管理、独占模式(acquire/release)和共享模式(acquireShared/releaseShared)、ReentrantLock和CountDownLatch如何基于AQS实现,附完整源码分析和面试模拟。

一句话总结

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 之前就设置好了,更可靠。