前言
上周面试,面试官问我:"你能说说 Java 的AQS基于管程是如何实现的吗?" 我当时只背了概念,结果当场翻车。回来后我花了 3 天,把 AQS源码啃了一遍,整理出这篇能直接拿去面试的笔记。
1、什么是管程
管程是基于MESA模型实现的,管程中引入了条件变量的概念,而且每个条件变量都对应有一个等待队列。MESA 模型中,条件变量可以有多个,Java 语言内置的管程里只有一个条件变量。
模型如下图所示:

⚠️提醒
✔️入口等待队列:多线程进入的时排队,只允许一个线程进入管程内部,其他线程等待
✔️条件变量和等待队列:解决线程同步的问题
2、Java 中管程(Monitor)的实现
- 其一是基于
Object监视器(Monitor)机制的内置synchronized同步;
 * **其二是基于抽象队列同步器(AQS)构建的`java.util.concurrent.locks.Lock`显式锁机制。** *** ** * ** *** ## 一、**AQS原理分析** ### **1、什么是AQS** java.util.concurrent 包中的同步器大多构建在一些共同的基础行为之上,例如等待队列、条件队列、独占获取与共享获取等。这些行为被抽象为一个统一的框架------**AbstractQueuedSynchronizer(简称 AQS)** 。 AQS 是一个用于实现依赖状态型同步器的抽象同步框架,为构建各种同步机制提供了基础支持。 ### 2、AQS实现方式 * 一般是通过一个内部类Sync继承 AQS * 将同步器所有调用都映射到Sync对应的方法 **2.1、举个例子** 🌰🌰 **(ReentrantLock)** ```scala public class ReentrantLock implements Lock, java.io.Serializable { private static final long serialVersionUID = 7373984872572414699L; ...... private final Sync sync; abstract static class Sync extends AbstractQueuedSynchronizer {} ...... } ``` ### **3、AQS具备的特性** * 阻塞等待队列 * 共享/独占 * 公平/非公平 * 可重入 * 允许中断 *** ** * ** *** ## 二、**AQS核心结构** ### **1、** AQS核心**源码** ```csharp public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable { static { try { stateOffset = unsafe.objectFieldOffset (AbstractQueuedSynchronizer.class.getDeclaredField("state")); headOffset = unsafe.objectFieldOffset (AbstractQueuedSynchronizer.class.getDeclaredField("head")); tailOffset = unsafe.objectFieldOffset (AbstractQueuedSynchronizer.class.getDeclaredField("tail")); waitStatusOffset = unsafe.objectFieldOffset (Node.class.getDeclaredField("waitStatus")); nextOffset = unsafe.objectFieldOffset (Node.class.getDeclaredField("next")); } catch (Exception ex) { throw new Error(ex); } } ..... //链表头节点 private transient volatile Node head; // 链表尾节点 private transient volatile Node tail; //共享变量,使用volatile修饰保证线程可见性 private volatile int state; //获取状态 protected final int getState() { return state; } //设置状态 protected final void setState(int newState) { state = newState; } //CAS操作:将同步状态值设置为给定值update如果当前同步状态的值等于expect(期望值) //stateOffset:就是定义的state值 protected final boolean compareAndSetState(int expect, int update) { // See below for intrinsics setup to support this return unsafe.compareAndSwapInt(this, stateOffset, expect, update); } ..... } ``` > ⚠️AQS 的关键实现基于以下两点核心机制: > > ✔️ **关键字段的可见性** > > 在 AQS 源码中,用于表示同步状态的 `state`、以及等待队列的头尾指针 `head` 和 `tail` 均被声明为 `volatile`,确保这些状态在多线程之间的立即可见性。 > > ✔️ **基于 CAS 的无锁竞争设计** > > 获取锁的核心逻辑依赖于对 `state` 字段的 **CAS(Compare-And-Swap)操作**,通过这一原子性操作实现无锁化的线程竞争与状态更新,从而避免传统锁机制带来的阻塞开销。 ### 2、**AQS两种队列** **两种队列结构示意图**  **2.1、同步等待队列** 负责管理在竞争锁失败时进入等待状态的线程,基于双向链表数据结构的队列,是FIFO先进先出线程等待队列,Java中的[CLH队列](https://link.juejin.cn?target=https%3A%2F%2Fzhida.zhihu.com%2Fsearch%3Fcontent_id%3D270246982%26content_type%3DArticle%26match_order%3D1%26q%3DCLH%25E9%2598%259F%25E5%2588%2597%26zhida_source%3Dentity "https://zhida.zhihu.com/search?content_id=270246982&content_type=Article&match_order=1&q=CLH%E9%98%9F%E5%88%97&zhida_source=entity")是原CLH队列的一个变种,**线程由原自旋机制改为阻塞机制**。  **2.1.1、公平锁** 新线程到来时,会先检查同步队列中是否有已在等待的线程。**只要有排队者**,它就会直接进入队列尾部等待,不会尝试获取锁。这是其"公平"的核心。如果队列为空,它仍会尝试获取锁。 **2.1.2、非公平锁** 新线程到来时,无论队列是否为空,都会**先尝试一次CAS操作去"插队"抢锁**。只有抢锁失败后,才会进入队列尾部排队。 > ⚠️提示 > ***✔️AQS默认采用非公平策略,给予了新线程一次"插队"的机会,旨在减少线程切换频率、提升整体吞吐性能。*** > ***✔️线程进入同步队列后,会因无法立即获取锁而发生实际的挂起与唤醒,这一过程涉及内核态切换与上下文恢复,因而存在一定的性能开销*** **2.2、条件等待队列** 与特定条件关联,当线程调用 `await()` 时,**会释放已持有的锁并进入条件队列等待**; 当其他线程调用 `signal()` 唤醒时,**该队列中的线程结点会被移至同步等待队列,重新参与锁的竞争**。  **2.2.1、** 条件**队列源码** 条件队列唤醒 ```java public class ConditionObject implements Condition, java.io.Serializable { private static final long serialVersionUID = 1173984872572414699L; /** First node of condition queue. */ private transient Node firstWaiter; /** Last node of condition queue. */ private transient Node lastWaiter; ...... public final void signal() { if (!isHeldExclusively()) throw new IllegalMonitorStateException(); Node first = firstWaiter; if (first != null) doSignal(first); } // 唤醒 private void doSignal(Node first) { do { if ( (firstWaiter = first.nextWaiter) == null) lastWaiter = null; first.nextWaiter = null; } while (!transferForSignal(first) && (first = firstWaiter) != null); } /** * 将节点从条件队列转移到同步队列 */ final boolean transferForSignal(Node node) { if (!compareAndSetWaitStatus(node, Node.CONDITION, 0)) return false; Node p = enq(node); int ws = p.waitStatus; if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL)) LockSupport.unpark(node.thread); return true; } } ``` 条件队列转换为同步队列 ```ini private Node enq(final Node node) { for (;;) { Node t = tail; if (t == null) { // Must initialize if (compareAndSetHead(new Node())) tail = head; } else { node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; } } } } ``` > ⚠️注意 > > ✔️基于*LockSupport.unpark(node.thread)、LockSupport.park(node.thread)* 实现阻塞和唤醒 > > ✔️**条件队列在AQS内部是一个单向链表,节点在被转移到同步队列时,被构建为双向链表节点CLH队列** **2.3、区别对比** | 特性 | 条件等待队列 | 同步等待队列 | |------|--------------------------|-----------------------------------| | 数据结构 | 单向链表 | 双向链表 | | 连接指针 | nextWaiter | prev, next | | 主要操作 | await(尾部添加),signal(头部移除) | acquire(尾部添加/自旋),release(头部唤醒/移除) | | 关键需求 | 简单的 FIFO 等待/唤醒 | 必须支持从任意位置取消节点、状态传播、稳定的遍历 | | 设计目标 | 实现简单,节省资源 | 功能完备,操作高效且安全 | > ⚠️注意 > > ✔️节点从**单向的条件队列** 转移到**双向的同步队列** 时,其数据结构确实发生了改变,这是由两个队列所承担的不同职责和操作需求决定的。 > > ✔️这种设计是AQS既能保持高效,又能支持复杂同步语义(如超时、中断、共享模式)的基础之一。 *** ** * ** *** ## 三、总结 AQS**不是对管程的简单模仿,而是用轻量级队列算法在 Java 语言层面重新发明了管程**。它将操作系统 / JVM 的管程概念抽象为可组合的 Java 组件,使得开发者能够以极低的成本构建出高性能、高可控的同步工具。