核心原理探索)
在 Java 并发编程的世界里有一个“隐形基石”——AbstractQueuedSynchronizer简称 AQS。它就像一个通用的“同步框架模板”几乎支撑了 Java 并发包中所有核心同步工具的实现从我们日常开发中常用的 ReentrantLock可重入锁到协调多线程等待的 CountDownLatch倒计时器、控制资源访问数量的 Semaphore信号量再到线程池 ThreadPoolExecutor 底层的同步控制其核心逻辑都源自 AQS。AQS 的神奇之处在于它通过巧妙的“状态控制”和“队列管理”将同步器的共性逻辑如线程排队、唤醒与个性逻辑如是否允许线程获取锁分离让开发者只需重写少量方法就能快速实现自定义同步器。本文将从设计定位、内部结构、核心流程、实际应用四个维度深入拆解 AQS 的底层原理结合代码示例和实战场景帮你彻底吃透这个 Java 并发的“灵魂组件”同时适配面试高频考点让知识既懂又会用。一、AQS 的设计定位同步器的通用骨架AQS 是一个抽象类位于 java.util.concurrent.locks 包下其核心设计目标是为各种同步器提供统一的基础框架。它封装了同步状态的管理、线程的排队等待、唤醒等核心操作开发者无需关注这些复杂的底层细节只需根据自身需求重写少量钩子方法就能实现符合业务场景的同步器。1.1 核心设计思想状态控制 队列管理AQS 的设计精髓可以用一句话概括用一个volatile状态变量控制访问权限用一个双向队列管理等待线程二者协同工作实现高效的同步控制。状态控制AQS 内部维护一个被 volatile 修饰的 int 变量 state用于表示同步状态。这个状态的具体含义由子类自行定义AQS 只提供统一的状态操作方法getState()、setState()、compareAndSetState()确保状态操作的线程安全性。队列管理当线程尝试获取同步状态失败时AQS 会将该线程封装成一个节点Node加入到一个双向链表结构的同步队列中让线程进入等待状态当同步状态被释放时AQS 会从队列中唤醒一个或多个等待线程让它们重新尝试获取同步状态。这种设计的优势在于“解耦”——将“如何实现同步”的共性逻辑排队、唤醒、状态原子操作与“是否允许访问”的个性逻辑如锁的重入、许可数量判断分离极大简化了同步器的实现难度。比如ReentrantLock 关注“锁的重入”CountDownLatch 关注“计数器是否为0”它们只需重写判断逻辑其余的排队、唤醒逻辑都直接复用 AQS 的模板方法。1.2 核心方法与模板模式AQS 采用模板模式定义了同步操作的完整骨架模板方法负责调用钩子方法实现统一的同步逻辑钩子方法由子类重写定义具体的同步规则。这种模式既能保证同步逻辑的一致性又能满足不同同步器的个性化需求。AQS 中需要子类重写的核心钩子方法默认抛出 UnsupportedOperationException必须按需重写如下表所示方法功能描述适用场景protected boolean tryAcquire(int arg)独占式获取同步状态返回true表示获取成功ReentrantLock独占锁protected boolean tryRelease(int arg)独占式释放同步状态返回true表示释放成功完全释放ReentrantLockprotected int tryAcquireShared(int arg)共享式获取同步状态返回值≥0表示成功返回值为剩余可用资源数0表示失败CountDownLatch、Semaphoreprotected boolean tryReleaseShared(int arg)共享式释放同步状态返回true表示释放成功且后续线程可继续获取CountDownLatch、Semaphoreprotected boolean isHeldExclusively()判断当前线程是否独占同步状态ReentrantLock判断当前线程是否持有锁AQS 提供的模板方法如 acquire()、release()、acquireShared()、releaseShared()会调用上述钩子方法串联起完整的同步逻辑。例如ReentrantLock 重写 tryAcquire() 和 tryRelease()实现独占式锁的获取与释放CountDownLatch 重写 tryAcquireShared() 和 tryReleaseShared()实现共享式同步多个线程等待计数器归0ReentrantReadWriteLock 则通过重写上述方法结合 state 位运算实现读写分离锁。二、AQS 的内部结构状态与队列的协作AQS 的内部结构主要由两部分组成同步状态state和同步队列CLH 队列。这两部分的协同工作是 AQS 实现同步控制的核心也是理解 AQS 原理的关键。2.1 同步状态state的设计state 是 AQS 中最核心的变量被 volatile 修饰用于存储同步状态其具体含义由子类定义不同的同步器对 state 的解读完全不同。2.1.1 state 的常见含义面试高频ReentrantLockstate 表示锁的重入次数。0 表示锁未被持有≥1 表示锁被持有值为几表示重入几次。例如线程A获取锁后state1再次重入锁state2释放一次state1完全释放state0。Semaphorestate 表示可用许可的数量。例如Semaphore(5) 初始化时 state5一个线程获取许可state4释放许可state5。CountDownLatchstate 表示计数器的初始值。例如CountDownLatch(3) 初始化时 state3每次调用 countDown()state 减1当 state0 时所有等待线程被唤醒。2.1.2 state 的线程安全性保证由于 state 被 volatile 修饰保证了线程间的可见性一个线程修改 state 后其他线程能立即看到最新值同时AQS 通过 Unsafe 类的 CAS 操作保证 state 操作的原子性。AQS 提供了三个核心方法操作 state// AQS中state的定义与核心操作 private volatile int state; // 获取当前同步状态 protected final int getState() { return state; } // 设置同步状态无原子性保证适用于已获取锁的线程无需CAS protected final void setState(int newState) { state newState; } // CAS原子性更新state预期值为expect更新为update失败返回false protected final boolean compareAndSetState(int expect, int update) { // 调用Unsafe的CAS操作保证原子性 return unsafe.compareAndSwapInt(this, stateOffset, expect, update); }这里需要注意setState() 方法没有原子性保证因为它仅在线程已获取同步状态如已持有锁的场景下使用此时不存在并发修改问题而 compareAndSetState() 是线程安全的适用于多个线程竞争同步状态的场景如锁的获取。2.2 同步队列CLH 队列的结构当线程尝试获取同步状态失败时AQS 会将该线程封装成一个 Node节点加入到同步队列中。这个同步队列是一个双向链表基于 CLHCraig, Landin, and Hagersten锁队列改进而来核心特点是 FIFO先进先出和自旋等待能高效实现线程的排队与唤醒。2.2.1 节点Node的核心结构每个 Node 节点对应一个等待线程内部包含多个核心字段用于记录线程状态、前后节点等信息源码如下简化版保留核心字段static final class Node { // 节点模式独占模式EXCLUSIVE、共享模式SHARED static final Node EXCLUSIVE null; static final Node SHARED new Node(); // 节点状态5种状态决定节点的行为面试高频考点 volatile int waitStatus; // 状态常量已取消超时或被中断不再参与竞争 static final int CANCELLED 1; // 状态常量后继节点需要被唤醒当前节点释放锁时需唤醒后继 static final int SIGNAL -1; // 状态常量节点处于条件队列中等待被唤醒 static final int CONDITION -2; // 状态常量共享模式下状态需向后传播如CountDownLatch static final int PROPAGATE -3; // 前驱节点双向链表 volatile Node prev; // 后继节点双向链表 volatile Node next; // 当前节点关联的线程 volatile Thread thread; // 条件队列中的后继节点用于Condition机制 Node nextWaiter; }节点状态waitStatus是面试中的高频考点需重点掌握每种状态的含义CANCELLED1节点已取消。当线程等待超时或被中断时节点会被标记为 CANCELLED不再参与同步竞争后续会被垃圾回收。SIGNAL-1后继节点需要被唤醒。当前节点释放同步状态后必须唤醒其后继节点让后继节点重新尝试获取状态。CONDITION-2节点处于条件队列中。当线程调用 Condition.await() 时会从同步队列转移到条件队列节点状态设为 CONDITION等待被 signal() 唤醒。PROPAGATE-3共享模式下的状态传播。当一个节点获取共享状态成功后需将状态传播给后续节点让其他等待的共享线程也能获取状态如 CountDownLatch 计数器归0后所有等待线程都需被唤醒。0初始状态节点刚创建时的默认状态无特殊含义。2.2.2 队列的头节点与尾节点AQS 通过两个 volatile 指针head、tail维护同步队列的头和尾确保队列操作的线程安全性// AQS中队列的头、尾指针transient表示不序列化 private transient volatile Node head; private transient volatile Node tail;队列的初始化与维护规则队列初始时为空head 和 tail 均为 null当第一个线程获取同步状态失败时会创建一个哨兵节点不关联线程作为头节点同时将当前线程封装为节点作为尾节点此时 head tail 哨兵节点后续线程获取状态失败时会通过 CAS 操作将自己的节点加入队列尾部保证入队的原子性头节点始终表示“当前持有同步状态的线程”或已成功获取状态的线程当头节点释放状态后会唤醒其后继节点后继节点获取状态成功后会成为新的头节点原头节点被垃圾回收。这里的哨兵节点设计很巧妙它不关联具体线程仅作为队列的“占位符”避免了头节点为空的判断逻辑简化了队列的操作流程。三、独占式同步获取与释放的完整流程独占式同步是 AQS 最常用的模式核心特点是同一时间只有一个线程能获取同步状态其他线程需排队等待。典型应用是 ReentrantLock独占锁。AQS 通过 acquire(int arg)获取状态和 release(int arg)释放状态两个模板方法实现独占式同步的完整流程。3.1 独占式获取acquire流程acquire(int arg) 方法的核心逻辑尝试获取状态 → 失败则入队 → 自旋等待 → 唤醒后重试直至获取状态成功或被中断。具体步骤拆解如下结合源码分析易懂好记步骤1尝试获取同步状态调用子类重写的 tryAcquire(arg) 方法尝试获取同步状态。如果返回 true表示获取成功直接返回线程继续执行如果返回 false表示获取失败进入下一步。步骤2获取失败封装节点入队将当前线程封装为 Node.EXCLUSIVE独占模式节点通过 addWaiter(Node mode) 方法将节点加入队列尾部。addWaiter 方法会先尝试快速入队如果尾节点不为 null直接通过 CAS 设置新尾节点如果快速入队失败如队列未初始化、CAS 竞争失败则调用 enq(Node node) 方法通过自旋确保节点成功入队。// 独占式获取状态的核心模板方法 public final void acquire(int arg) { // 1. 尝试获取状态2. 失败则入队3. 入队后自旋等待若被中断则记录 if (!tryAcquire(arg) acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) { selfInterrupt(); // 若线程在等待过程中被中断恢复中断状态 } } // 将线程封装为节点加入队列尾部 private Node addWaiter(Node mode) { Node node new Node(Thread.currentThread(), mode); Node pred tail; // 快速入队如果尾节点不为null直接CAS设置新尾节点 if (pred ! null) { node.prev pred; if (compareAndSetTail(pred, node)) { pred.next node; return node; } } // 快速入队失败自旋入队确保入队成功 enq(node); return node; } // 自旋入队初始化队列并确保节点入队 private Node enq(final Node node) { for (;;) { // 自旋死循环直到入队成功 Node t tail; if (t null) { // 队列未初始化创建哨兵节点作为头节点 if (compareAndSetHead(new Node())) { tail head; } } else { // 队列已初始化CAS设置新尾节点 node.prev t; if (compareAndSetTail(t, node)) { t.next node; return t; } } } }步骤3自旋等待阻塞线程节点入队后调用 acquireQueued(Node node, int arg) 方法让节点进入自旋状态不断尝试获取同步状态具体逻辑如果当前节点的前驱是头节点说明当前节点是队列中的第一个等待线程再次尝试调用 tryAcquire(arg) 获取状态如果获取成功将当前节点设为新的头节点原头节点被垃圾回收返回 false表示未被中断自旋结束如果获取失败判断前驱节点的状态若前驱节点状态为 SIGNAL表示会唤醒后继节点则通过 LockSupport.park(this) 阻塞当前线程若前驱节点状态为 CANCELLED则移除该前驱节点继续自旋线程被阻塞后会等待被唤醒前驱节点释放状态时会唤醒它。步骤4唤醒后处理线程被唤醒后会继续重复步骤3的自旋逻辑直至获取同步状态成功如果线程在等待过程中被中断会记录中断状态待获取状态成功后调用 selfInterrupt() 恢复中断状态保证中断机制的正确性。3.2 独占式释放release流程release(int arg) 方法的核心逻辑尝试释放状态 → 成功则唤醒后继节点具体步骤如下步骤1尝试释放同步状态调用子类重写的 tryRelease(arg) 方法尝试释放同步状态。如果返回 true表示释放成功完全释放如 ReentrantLock 的 state 减至 0如果返回 false表示释放失败如未完全释放重入锁直接返回 false。步骤2唤醒后继节点如果释放成功获取当前头节点若头节点不为 null 且状态不为 0表示有等待线程调用 unparkSuccessor(Node node) 方法唤醒头节点的后继节点。unparkSuccessor 方法的逻辑的是先清除头节点的状态将 SIGNAL 设为 0然后查找头节点的后继节点如果后继节点为 null 或已被取消waitStatus 0则从队列尾部向前查找第一个有效节点waitStatus ≤ 0最后通过 LockSupport.unpark(s.thread) 唤醒该节点对应的线程。// 独占式释放状态的核心模板方法 public final boolean release(int arg) { if (tryRelease(arg)) { // 尝试释放状态成功则唤醒后继节点 Node h head; if (h ! null h.waitStatus ! 0) { unparkSuccessor(h); // 唤醒后继节点 } return true; } return false; } // 唤醒当前节点的后继节点 private void unparkSuccessor(Node node) { int ws node.waitStatus; if (ws 0) { // 清除节点状态SIGNAL → 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); // 唤醒线程 } }这里有一个关键细节为什么要从尾节点向前查找有效节点因为在多线程并发入队时可能存在“节点的 next 指针还未更新”的情况CAS 设置尾节点成功但前驱节点的 next 指针未及时赋值从尾部向前查找能确保找到真正的后继有效节点避免遗漏。四、共享式同步多线程共享资源的实现共享式同步与独占式同步的核心区别在于允许多个线程同时获取同步状态只要资源充足多个线程可以同时成功获取。典型应用有 CountDownLatch倒计时器、Semaphore信号量、CyclicBarrier循环屏障等。AQS 通过 acquireShared(int arg)获取状态和 releaseShared(int arg)释放状态两个模板方法实现共享式同步。4.1 共享式获取acquireShared流程acquireShared(int arg) 方法的核心逻辑与独占式类似但允许多个线程同时获取状态具体步骤步骤1尝试获取共享状态调用子类重写的 tryAcquireShared(arg) 方法尝试获取共享状态。返回值 ≥ 0 表示获取成功返回值为剩余可用资源数返回值 0 表示获取失败进入下一步。步骤2获取失败封装节点入队调用 doAcquireShared(arg) 方法将当前线程封装为 Node.SHARED共享模式节点通过 addWaiter(Node.SHARED) 方法加入队列尾部入队逻辑与独占式一致。步骤3自旋等待阻塞线程节点入队后进入自旋状态具体逻辑如果当前节点的前驱是头节点再次尝试调用 tryAcquireShared(arg) 获取状态如果获取成功返回值 ≥ 0调用 setHeadAndPropagate(node, r) 方法将当前节点设为新头节点并传播状态唤醒后续共享节点如果获取失败判断前驱节点的状态若符合条件则阻塞当前线程等待被唤醒线程被唤醒后重复上述步骤直至获取状态成功或被中断。// 共享式获取状态的核心模板方法 public final void acquireShared(int arg) { if (tryAcquireShared(arg) 0) { // 获取失败入队等待 doAcquireShared(arg); } } // 共享式入队后自旋等待 private void doAcquireShared(int arg) { final Node node addWaiter(Node.SHARED); boolean failed true; try { boolean interrupted false; for (;;) { final Node p node.predecessor(); // 获取前驱节点 if (p head) { // 前驱是头节点再次尝试获取 int r tryAcquireShared(arg); if (r 0) { // 设为头节点并传播状态唤醒后续共享节点 setHeadAndPropagate(node, r); p.next null; // 帮助GC断开与原头节点的关联 if (interrupted) { selfInterrupt(); // 恢复中断状态 } failed false; return; } } // 阻塞当前线程直至被唤醒 if (shouldParkAfterFailedAcquire(p, node) parkAndCheckInterrupt()) { interrupted true; } } } finally { if (failed) { cancelAcquire(node); // 获取失败取消当前节点 } } }关键状态传播setHeadAndPropagate共享模式与独占模式的核心区别之一就是“状态传播”。当一个共享节点获取状态成功后不仅要将自己设为头节点还要唤醒后续的共享节点让其他等待的共享线程也能获取状态。例如CountDownLatch 的计数器归 0 后所有等待的线程都应被唤醒这就是通过状态传播实现的。setHeadAndPropagate 方法的逻辑先将当前节点设为头节点然后判断是否需要传播状态如剩余资源充足、节点是共享模式如果需要调用 doReleaseShared() 方法唤醒后继节点实现状态的链式传播。4.2 共享式释放releaseShared流程releaseShared(int arg) 方法的核心逻辑尝试释放状态 → 成功则唤醒后继节点且支持状态传播具体步骤步骤1尝试释放共享状态调用子类重写的 tryReleaseShared(arg) 方法尝试释放共享状态。返回 true 表示释放成功且后续线程可继续获取返回 false 表示释放失败直接返回 false。步骤2唤醒后继节点传播状态如果释放成功调用 doReleaseShared() 方法唤醒队列中的后继节点且支持状态传播即唤醒一个节点后该节点获取状态成功后会继续唤醒下一个共享节点。// 共享式释放状态的核心模板方法 public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { // 释放成功唤醒后继节点并传播状态 doReleaseShared(); return true; } return false; } // 共享式唤醒支持状态传播 private void doReleaseShared() { for (;;) { Node h head; if (h ! null h ! tail) { // 队列不为空 int ws h.waitStatus; if (ws Node.SIGNAL) { // 后继节点需要唤醒 if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) { continue; // CAS失败重试 } unparkSuccessor(h); // 唤醒后继节点 } else if (ws 0 !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) { continue; // 设为PROPAGATE确保状态传播 } } if (h head) { // 头节点未变化退出循环避免无限自旋 break; } } }doReleaseShared() 方法通过自旋确保唤醒操作成功如果头节点状态为 SIGNAL唤醒后继节点如果头节点状态为 0将其设为 PROPAGATE确保后续节点获取状态后能继续传播状态。这种设计能保证共享模式下所有等待的线程都能被唤醒实现资源的高效共享。五、条件队列线程间协作的补充机制AQS 除了同步队列还提供了条件队列Condition机制用于实现线程间的精准协作类似于 synchronized 中的 wait()/notify()但比其更灵活——一个同步器可以对应多个条件队列不同的条件队列可以实现不同的等待/唤醒逻辑。Condition 的实现依赖于 AQS 的 Node 结构与同步队列形成互补同步队列用于管理“获取同步状态失败”的线程条件队列用于管理“等待特定条件”的线程。5.1 条件队列的结构每个 Condition 对象对应一个单向链表的条件队列节点类型为 Node.CONDITION节点状态为 CONDITION。当线程调用 Condition.await() 时会从同步队列转移到条件队列并阻塞当调用 Condition.signal() 或 Condition.signalAll() 时会将条件队列中的节点转移到同步队列等待获取同步状态。5.2 核心操作await() 与 signal()5.2.1 await() 方法线程等待线程调用 Condition.await() 方法的流程释放当前持有的同步状态调用 release() 方法将当前线程封装为 Node.CONDITION 节点加入到条件队列尾部通过 LockSupport.park(this) 阻塞当前线程等待被唤醒线程被唤醒后从条件队列转移到同步队列重新尝试获取同步状态成功后继续执行。5.2.2 signal() 方法唤醒线程线程调用 Condition.signal() 方法的流程获取条件队列的头节点将该头节点从条件队列中移除转移到同步队列尾部将节点状态从 CONDITION 改为 0唤醒该节点对应的线程让其重新尝试获取同步状态。signalAll() 方法与 signal() 类似区别在于signal() 只唤醒条件队列的头节点而 signalAll() 唤醒条件队列的所有节点将它们全部转移到同步队列。5.3 实战示例Condition 的应用以下示例通过 ReentrantLock 和 Condition实现“线程A等待某个条件满足线程B触发条件后唤醒线程A”的场景直观理解条件队列与同步队列的交互public class ConditionExample { // 基于AQS实现的ReentrantLock private final Lock lock new ReentrantLock(); // 基于AQS实现的Condition条件队列 private final Condition condition lock.newCondition(); // 自定义条件flag为true时线程A可继续执行 private boolean flag false; // 线程A等待flag为true public void waitForFlag() throws InterruptedException { lock.lock(); // 先获取同步状态锁 try { // 循环判断条件避免虚假唤醒面试高频考点 while (!flag) { condition.await(); // 释放锁加入条件队列并阻塞 } System.out.println(Flag is true, continue working); } finally { lock.unlock(); // 确保释放锁 } } // 线程B设置flag为true唤醒线程A public void setFlag() { lock.lock(); // 获取同步状态锁 try { flag true; condition.signal(); // 将条件队列的头节点转移到同步队列唤醒线程A } finally { lock.unlock(); // 释放锁唤醒同步队列中的线程A } } // 测试 public static void main(String[] args) throws InterruptedException { ConditionExample example new ConditionExample(); // 线程A等待flag new Thread(() - { try { example.waitForFlag(); } catch (InterruptedException e) { e.printStackTrace(); } }, Thread-A).start(); // 线程B延迟1秒设置flag并唤醒线程A Thread.sleep(1000); new Thread(example::setFlag, Thread-B).start(); } }代码说明线程A调用 waitForFlag() 时先获取锁同步状态发现 flag 为 false调用 condition.await()释放锁并加入条件队列进入阻塞状态线程B调用 setFlag()获取锁后将 flag 设为 true调用 condition.signal()将条件队列中的线程A节点转移到同步队列线程B释放锁后唤醒同步队列中的线程A线程A重新尝试获取锁获取成功后再次判断 flag 为 true继续执行。这里有一个面试高频考点为什么要用 while (!flag) 判断条件而不是 if因为线程可能会被“虚假唤醒”没有调用 signal()线程也可能被唤醒用 while 循环可以确保线程被唤醒后再次检查条件避免条件不满足时继续执行。六、AQS 的应用并发工具的底层依赖理解 AQS 的核心价值不仅在于掌握其底层原理更在于能看透 Java 并发工具的实现逻辑。以下介绍几个常用并发工具如何基于 AQS 实现帮你打通“底层原理”与“实战应用”的关联。6.1 ReentrantLock 与 AQS独占式应用ReentrantLock 是 AQS 最典型的应用实现了独占式可重入锁支持公平锁和非公平锁两种模式其核心逻辑就是重写 AQS 的独占式钩子方法。核心实现逻辑tryAcquire(int arg)通过 CAS 尝试获取锁支持重入和公平性判断。非公平锁默认直接尝试 CAS 将 state 从 0 改为 1成功则获取锁失败则检查当前线程是否是持有锁的线程重入若是则 state 加 1否则入队。公平锁获取锁前先检查同步队列中是否有前驱节点是否有线程排队若无则尝试 CAS 获取锁若有则入队保证线程按 FIFO 顺序获取锁。tryRelease(int arg)释放锁将 state 减 1当 state 减至 0 时表示完全释放锁返回 true唤醒后继节点否则返回 false未完全释放。isHeldExclusively()判断当前线程是否是持有锁的线程用于 Condition 机制的判断。公平锁与非公平锁的性能差异面试高频非公平锁吞吐量更高。因为省去了“检查队列”的步骤线程可以直接尝试获取锁减少了队列操作的开销适合高并发场景但可能导致线程饥饿某些线程长期无法获取锁。公平锁安全性更高。保证线程按排队顺序获取锁避免饥饿但频繁的队列检查和操作会增加开销吞吐量低于非公平锁。6.2 CountDownLatch 与 AQS共享式应用CountDownLatch 用于实现“一个线程等待多个线程完成操作后再继续执行”其核心是基于 AQS 的共享式同步state 表示计数器的初始值。核心实现逻辑初始化创建 CountDownLatch 时传入计数器值 NAQS 的 state 被设为 N。tryAcquireShared(int arg)判断 state 是否为 0若是则返回 0获取成功否则返回 -1获取失败进入队列等待。tryReleaseShared(int arg)通过 CAS 将 state 减 1当 state 减至 0 时返回 true触发 doReleaseShared() 方法唤醒所有等待的共享节点所有等待线程被唤醒否则返回 false。典型场景主线程等待 3 个子线程完成初始化子线程全部执行 countDown() 后主线程从 await() 返回继续执行后续逻辑。6.3 Semaphore 与 AQS共享式应用Semaphore信号量用于控制同时访问某个资源的线程数量其核心是基于 AQS 的共享式同步state 表示可用许可的数量。核心实现逻辑初始化创建 Semaphore 时传入许可数量 NAQS 的 state 被设为 N。tryAcquireShared(int arg)尝试获取 arg 个许可通过 CAS 减少 state 的值若剩余许可 ≥ 0则返回剩余许可获取成功否则返回 -1获取失败入队等待。非公平模式直接尝试 CAS 减少 state公平模式先检查队列无等待线程再尝试 CAS。tryReleaseShared(int arg)通过 CAS 增加 state 的值释放 arg 个许可返回 true唤醒后续等待线程。典型场景Semaphore(5) 允许 5 个线程同时获取许可访问某个资源第 6 个线程获取许可时会进入队列等待直到有线程释放许可。七、AQS 的设计智慧与局限AQS 的设计堪称 Java 并发编程的典范但其也存在一定的局限性。理解这些能帮助我们更合理地使用 AQS 及其衍生的并发工具。7.1 设计亮点面试高频模板模式的极致应用将同步器的共性逻辑排队、唤醒、状态原子操作抽象为模板方法个性逻辑状态判断通过钩子方法留给子类实现极大降低了同步器的实现难度实现了“代码复用”与“灵活扩展”的平衡。高效的队列管理基于 CLH 队列改进的双向链表结合 CAS 操作实现无锁入队避免了线程阻塞带来的上下文切换开销哨兵节点的设计简化了队列操作逻辑。多模式支持同时支持独占式和共享式同步满足不同的业务场景如锁的独占访问、资源的共享访问。内存可见性与原子性保证state 被 volatile 修饰保证线程间的可见性通过 Unsafe 类的 CAS 操作保证 state 操作的原子性避免并发安全问题。7.2 局限性单一状态变量AQS 只维护一个 int 类型的 state 变量对于复杂的同步器如 ReentrantReadWriteLock需要通过位运算拆分 state如高 16 位表示读锁低 16 位表示写锁增加了实现复杂度。线程唤醒的不确定性线程唤醒依赖 LockSupport.unpark()但线程何时被操作系统调度执行是不确定的可能存在唤醒延迟影响并发性能。自定义难度高开发者需要深入理解 AQS 的底层逻辑队列管理、状态传播、阻塞唤醒才能正确重写钩子方法否则易出现死锁、线程饥饿、并发安全等问题。八、总结AQS 在并发体系中的地位AQS 是 Java 并发编程的“基础设施”是连接底层同步机制与上层并发工具的桥梁。它的核心价值不在于自身能实现某种同步功能而在于提供了一个通用的同步框架让开发者无需重复实现复杂的排队、唤醒逻辑只需专注于业务层面的同步规则。理解 AQS不仅能让我们看透 ReentrantLock、CountDownLatch、Semaphore 等常用并发工具的底层实现更能帮助我们领会 Java 并发编程的核心思想——“用状态控制访问权限用队列管理等待线程”。这种思想不仅适用于 Java也适用于其他语言的并发编程是解决复杂并发问题的通用思路。对于开发者而言掌握 AQS 不仅是面试加分项更是提升并发编程能力的关键。只有深入理解底层原理才能在实际开发中合理选择并发工具规避并发风险写出高效、安全的并发代码。最后留给大家一个思考问题ReentrantReadWriteLock 是如何通过 AQS 的 state 位运算实现读写锁分离的欢迎在评论区留言讨论