【多线程】---AQS 原理 知识点(设计思想与源码深度解析)

基于:JDK 17(java.util.concurrent.locks.AbstractQueuedSynchronizer,JDK 1.5 引入,8~17 核心逻辑基本稳定)

AQS(AbstractQueuedSynchronizer,抽象队列同步器)是整个 JUC 并发包的"地基"------ReentrantLock、Semaphore、CountDownLatch、ReentrantReadWriteLock、ThreadPoolExecutor.Worker 全部建立在它之上。本文从设计思想出发,剖析它的同步状态、CLH 变体等待队列、模板方法与 Condition 机制,用主线流程 + 可运行代码把"AQS 到底在做什么"讲透。


目录

  1. 核心理论
    • [1.1 一句话定位](#1.1 一句话定位)
    • [1.2 它解决什么问题](#1.2 它解决什么问题)
    • [1.3 基于 AQS 的并发工具全景](#1.3 基于 AQS 的并发工具全景)
    • [1.4 三大核心抽象](#1.4 三大核心抽象)
  2. 设计思想与核心机制
    • [2.1 总体设计思路](#2.1 总体设计思路)
    • [2.2 模板方法模式](#2.2 模板方法模式)
    • [2.3 同步状态 state](#2.3 同步状态 state)
    • [2.4 等待队列 CLH 变体](#2.4 等待队列 CLH 变体)
    • [2.5 节点 Node 与 waitStatus](#2.5 节点 Node 与 waitStatus)
    • [2.6 排队与出队机制](#2.6 排队与出队机制)
    • [2.7 阻塞与唤醒 LockSupport](#2.7 阻塞与唤醒 LockSupport)
    • [2.8 公平锁与非公平锁](#2.8 公平锁与非公平锁)
    • [2.9 中断与超时支持](#2.9 中断与超时支持)
    • [2.10 条件队列 Condition](#2.10 条件队列 Condition)
  3. 主线流程剖析
    • [3.1 独占获取加锁主线](#3.1 独占获取加锁主线)
    • [3.2 独占释放唤醒主线](#3.2 独占释放唤醒主线)
    • [3.3 共享模式获取与传播](#3.3 共享模式获取与传播)
    • [3.4 Condition 完整链路](#3.4 Condition 完整链路)
    • [3.5 一次完整的锁竞争时序](#3.5 一次完整的锁竞争时序)
  4. 类关系图
    • [4.1 JUC 继承全景](#4.1 JUC 继承全景)
    • [4.2 AQS 内部结构](#4.2 AQS 内部结构)
    • [4.3 同步队列与条件队列形态](#4.3 同步队列与条件队列形态)
  5. 动手实现一个同步器
    • [5.1 基于 AQS 的自定义互斥锁](#5.1 基于 AQS 的自定义互斥锁)
    • [5.2 升级为可重入与公平](#5.2 升级为可重入与公平)
    • [5.3 基于 AQS 实现共享闸门 MyLatch](#5.3 基于 AQS 实现共享闸门 MyLatch)
    • [5.4 对照与验证 Demo](#5.4 对照与验证 Demo)
  6. [扩展点与常见问题 FAQ](#扩展点与常见问题 FAQ)
    • [6.1 高频面试问题速答](#6.1 高频面试问题速答)
    • [6.2 源码细节点辨析](#6.2 源码细节点辨析)
    • [6.3 扩展实践方向与总结](#6.3 扩展实践方向与总结)

1. 核心理论 ★★★

1.1 一句话定位

关键认知 :AQS = 一个 volatile int 状态(state)+ 一个 CLH 变体线程等待队列 + 一套模板方法协议

所有"锁 / 信号量 / 门闩"本质上都只做一件事:尝试把 state 改成符合条件的值,改不成就排进队列等着别人改完叫醒自己

java 复制代码
// AbstractQueuedSynchronizer 字段一览(JDK 17,省略部分条件锁支持)
public abstract class AbstractQueuedSynchronizer
        extends AbstractOwnableSynchronizer implements java.io.Serializable {

    private transient volatile Node head;   // 同步队列头(哨兵,thread == null)
    private transient volatile Node tail;   // 同步队列尾

    private volatile int state;             // 核心:同步状态
    private transient Thread exclusiveOwnerThread; // 继承自 AbstractOwnableSynchronizer

    // state 的三种受保护访问方式
    protected final int getState() { return state; }
    protected final void setState(int newState) { state = newState; }
    protected final boolean compareAndSetState(int expect, int update) {
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }
}
  • state 用 volatile 保证可见性,用 CAS(compareAndSetState,底层 Unsafe) 保证原子修改;
  • 队列的 head / tail 均 volatile,入队用 CAS 尾插。

1.2 它解决什么问题

在 AQS 出现前,Java 里做线程互斥只有 synchronized:好用但不灵活------不能响应中断、不能超时、不能做公平排队、多条件等待要自己造、共享式获取(N 个许可)无从谈起。

AQS 把并发同步的"脏活"全部接管,只把一个问题留给子类:"此时你(调用方)能不能获得/释放这个资源?"

脏活(AQS 免费提供) 子类只需回答
抢锁失败的线程排队(CLH 队列) tryAcquire:能获得吗?
队首线程阻塞与唤醒(LockSupport) tryRelease:能释放吗?
中断传播、超时取消、节点清理 tryAcquireShared:还剩多少?
Condition 等待/通知协议 tryReleaseShared:归还后够吗?

这就是模板方法模式------变化的部分下沉给子类,不变的部分固化在父类。

1.3 基于 AQS 的并发工具全景

AQS 模式 state 的语义 抢不到怎么办
ReentrantLock 独占 持有计数(0=无锁,>0 表示重入次数) 入队 park,解锁时唤醒
ReentrantReadWriteLock 独占+共享 高 16 位=读锁持有数,低 16 位=写锁重入数 写锁独占、读锁可并发
Semaphore 共享 剩余许可数(permit) 许可为 0 时入队等待
CountDownLatch 共享 剩余倒计数 计数归 0 前 await 线程全部入队
ThreadPoolExecutor.Worker 独占(不可重入) 0/1:worker 是否"被占用" 核心线程数之外的抢占排队

⚠️ 注意 FutureTask 是反例 :JDK 5~7 的 FutureTask 基于 AQS 实现状态机;JDK 8 起为性能自研 volatile state + Unsafe CAS 甩开了 AQS。这说明 AQS 也有性能边界,不是万金油。

1.4 三大核心抽象

  1. 一个状态(state) :int 可以表达"有/无"(锁)、"还剩几个"(信号量)、"几重"(重入计数)、甚至切分成高低 16 位表达两种信息(读写锁)。这是它比 boolean locked 强大的根本原因。
  2. 一个等待队列(CLH 变体):抢不到 state 的线程排成队列,先进先出(按排队序获得唤醒机会,公平性由子类决定)。
  3. 一套协议(模板方法):独占/共享两套获取-释放钩子,配合可中断、可超时、条件等待的完整模板。

2. 设计思想与核心机制 ★★★

2.1 总体设计思路

AQS 的设计可以概括为一句:"把锁的语义做成一个可 CAS 的整数,把等锁的线程做成一条队列。"

  • 获得资源成功 → 修改 state 返回(不排队);
  • 获得资源失败 → 包装成 Node 塞进队尾,挂起自己(不空转);
  • 有人释放资源 → 从队头挑下一个线程唤醒,被唤醒者重新尝试抢 state;
  • 所有操作在无锁前提下靠 volatile + CAS 完成,不依赖任何系统级互斥。

设计目标:等待线程 O(1) 时间定位自己的邻居 (CAS 队尾入队、队头出队),并且不会饿死 (排队序),又不强制公平(把是否插队的决定权交给子类的 tryAcquire)。

2.2 模板方法模式

AQS 对子类暴露两套钩子方法(必须覆盖才能用),子类不同实现组合出不同同步器:

钩子方法 语义 覆盖者
tryAcquire(int arg) 独占模式尝试获取,成功返回 true ReentrantLock / ReentrantReadWriteLock 写锁 / ThreadPoolExecutor.Worker
tryRelease(int arg) 独占模式尝试释放 同上
tryAcquireShared(int arg) 共享模式尝试获取,返回剩余值(>=0 表示成功) Semaphore / CountDownLatch / 读锁
tryReleaseShared(int arg) 共享模式尝试释放 同上
isHeldExclusively() 当前线程是否独占持有(供 Condition 校验) 独占类同步器

模板方法本身都是 final,子类无法改写排队逻辑:

java 复制代码
// 独占获取模板:先问钩子,失败就排队等
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))  // 排队 + 阻塞,若被中断过则补中断
        selfInterrupt();
}

// 独占释放模板:钩子返回 true 才去唤醒队首后继
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);   // 唤醒头节点的下一个有效节点
        return true;
    }
    return false;
}

2.3 同步状态 state

  • state 是 int 类型、volatile 修饰,三种受保护操作:getState()setState()compareAndSetState()
  • 写者安全规则:"改 state 之前做的事" (如写入被保护的业务数据)对"之后读到新 state 的线程"可见------靠 volatile 的 happens-before。
  • 每个同步器自由定义 state 的 bit 布局:
同步器 state 布局 说明
ReentrantLock 整个 int = 重入次数 state = 0 无锁;每次 lock +1,unlock -1,归 0 才算真正释放
ReentrantReadWriteLock `高16位 低16位`
Semaphore 整个 int = 剩余许可 可负?不------许可不足时返回负剩余值给 AQS 表示"拿不到"(tryAcquireShared 返回值语义)
CountDownLatch 整个 int = 剩余倒数 0 表示门已开

细节tryAcquireShared 的返回值是三态 ------正数(成功且还有剩余)、0(成功但已耗尽)、负数(失败需排队)。CountDownLatch 的 tryAcquireShared 就写成 getState() == 0 ? 1 : -1

2.4 等待队列 CLH 变体

AQS 的队列基于 CLH 锁(Craig, Landin, Hagersten 三人的自旋锁)改造而来

对比项 原始 CLH 自旋锁 AQS 的 CLH 变体
排队介质 隐式前驱(只存前驱的锁状态,单链表) 显式双向链表(prev / next 都存)
等待方式 自旋轮询前驱 park 挂起,前驱释放时 unpark 唤醒(省 CPU)
取消节点 无需处理(无独立节点) 支持节点取消(超时/中断)后出链清理
状态传递 轮询前驱标志位 每个节点用 waitStatus 显式标记"我释放后会唤醒后继"

为什么改成双向?

  1. 找后继唤醒必须 O(1) :当前线程释放锁后要精确唤醒下一个等待者(队列是"排他性唤醒"),单向链做不到;
  2. 取消节点会断链 :被取消的节点把自己的 next 置为自身(help GC),后继节点不能依赖 next 向后遍历,只能从 tail 沿 prev 逆序找第一个有效节点(见 3.2 unparkSuccessor 的"倒着找"逻辑)。

2.5 节点 Node 与 waitStatus

java 复制代码
abstract static class Node {
    static final int CANCELLED =  1;   // 节点已取消(超时/中断放弃),永不再变
    static final int SIGNAL    = -1;   // 前驱承诺:释放后要唤醒我("我排你后面,你走时叫我")
    static final int CONDITION = -2;   // 节点在条件队列中,等 signal
    static final int PROPAGATE = -3;   // 共享模式:释放动作应继续向后传播

    volatile int waitStatus;    // 上述状态之一或 0(新建节点)
    volatile Node prev;         // 同步队列前驱
    volatile Node next;         // 同步队列后继
    volatile Thread thread;     // 排队的线程
    Node nextWaiter;            // 双用途:EXCLUSIVE=独占模式标记 / 条件队列的后继指针
}
waitStatus 含义 谁来设置
CANCELLED 1 放弃等待(中断/超时) cancelAcquire / transferForCancelledWait
SIGNAL -1 后继线程已 park,等前驱释放后唤醒它 shouldParkAfterFailedAcquire 把前驱置 SIGNAL
CONDITION -2 在条件队列中 await addConditionWaiter
PROPAGATE -3 共享释放要传播 doReleaseShared
0 0 初始状态 节点入队时

关键认知 :SIGNAL 不是节点自己的状态,而是给前驱看的委托书 ------"前驱,你释放后记得唤醒我的线程"。等待线程 park 前,必须确认自己的前驱被标记为 SIGNAL(shouldParkAfterFailedAcquire 干的就是这件事),否则可能存在"前驱释放时不知道要叫你"的竞态。

head哨兵节点 (thread == null),永远不代表等待线程,真正排队从 head.next 开始------这样队首线程被唤醒出队时,只需要把自己的后继设为新 head,删除自身,无需与队内其他节点竞争。

2.6 排队与出队机制

入队(addWaiter → enq):尾插 + CAS,失败重试直到成功:

java 复制代码
private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);  // mode = EXCLUSIVE / SHARED
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) { pred.next = node; return node; }
    }
    enq(node);   // 队列空或 CAS 失败,走自旋版
    return node;
}

private Node enq(final Node node) {
    for (;;) {                                    // 自旋直到成功
        Node t = tail;
        if (t == null) {                          // 队列为空:先初始化哨兵 head
            if (compareAndSetHead(new Node())) continue;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) { t.next = node; return t; }
        }
    }
}

等锁(acquireQueued) ------被唤醒后不是直接拿锁,而是回到循环重新抢:

java 复制代码
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)) {   // ① 只有前驱是 head 才有资格抢
                setHead(node);                    // ② 抢到:自己变成新哨兵 head,脱链
                p.next = null;                    // help GC
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&  // ③ 抢不到:给前驱挂牌 SIGNAL
                parkAndCheckInterrupt())                  // ④ 挂起自己;被唤醒后检查中断
                interrupted = true;
        }
    } finally {
        if (failed) cancelAcquire(node);          // tryAcquire 抛异常才走这里(取消清理)
    }
}

出队 :没有显式"删除"操作------抢到锁的节点把自己 setHead 成为新哨兵(head.thread = null),旧 head 靠 p.next = null 脱离引用后被 GC。队列里始终只留"哨兵 + 等待者"。

2.7 阻塞与唤醒 LockSupport

  • 队内线程用 LockSupport.park() 挂起,前驱释放锁后 LockSupport.unpark(thread) 唤醒------基于 Unsafe 的 park/unpark ,语义上等价于"每个线程一个许可(permit)":
    • unpark 发放许可(可提前发放,多次只算一次);
    • park 消费许可,没有许可就阻塞;若有线程被提前 unpark,park 立即返回不阻塞。
  • 为什么不自旋?自旋空转烧 CPU。AQS 把排队等待做成 park 阻塞,唤醒开销换 CPU 节省,只有在入队 CAS、抢锁这种纳秒级窗口才自旋。
  • 内存语义:unpark 之前线程做的事,对 park 返回后的线程可见(配合 volatile state 的读写构成 happens-before 链)。

2.8 公平锁与非公平锁

公平与否根本不在 AQS 里,而在于子类 tryAcquire 是否先看队列:

java 复制代码
// ReentrantLock.FairSync ------ 抢之前先问"队列里有没有人排在我前面"
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;
        }
    } else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;                // 重入 +1
        if (nextc < 0) throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

// ReentrantLock.NonfairSync ------ 进来先不管队列,直接 CAS 硬抢一次
final void lock() {
    if (compareAndSetState(0, 1))                // 插队:能抢到就直接持锁
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);                              // 抢不到才走排队(nonfairTryAcquire 内部仍有重入判断)
}

非公平锁为什么更快?

刚释放锁的线程此时 CPU 缓存里还热着(线程亲和性),让它立刻再抢一次,可以省掉一次唤醒→调度→切换 的完整开销;排队唤醒属于"高成本路径"。代价是极端情况下队首线程可能被持续插队饿死(吞吐换公平)。

2.9 中断与超时支持

AQS 对中断有两套策略,取决于走哪个模板:

模板方法 中断处理 适用
acquire(arg) park 醒来发现中断位 → 记录 interrupted=true继续排队抢锁 ,拿到锁后 selfInterrupt() 补回中断标志 Lock.lock() 的语义:不允许中断夺走锁,但中断状态会被保留
acquireInterruptibly(arg) park 中一旦被中断 → 立即抛 InterruptedException 并取消排队 Lock.lockInterruptibly()
tryAcquireNanos(arg, nanos) 超时前反复抢;到点还没抢到 → cancelAcquire 出队返回 false Lock.tryLock(timeout, unit)

parkAndCheckInterrupt() 里用 Thread.interrupted()会清除标志 )而不是 isInterrupted()

java 复制代码
private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();   // 必须清除:否则带着中断位再次 park 会立即返回,造成"忙等"
}

2.10 条件队列 Condition

ConditionObject 是 AQS 的内部类,对应 Object.wait/notify 的完整替代(一个锁可以 new 多个 Condition ,比内置 monitor 只有一个等待集强得多)。每个 Condition 维护一条单链表(用 Node.nextWaiter 串,头 firstWaiter / 尾 lastWaiter),节点状态为 CONDITION。

对比项 Object.wait / notify Condition.await / signal
等待集数量 每个锁 1 个 每把锁可建多个(如"队满等"与"队空等"分开)
中断响应 抛 InterruptedException await 抛 / awaitUninterruptibly 不抛
超时 不支持精确纳秒 awaitNanos / awaitUntil
结构 对象监视器(monitor) AQS 队列之外再加条件单链表

await 的设计精妙处在于:节点先挂进条件队列,再"完全释放"持有的锁 (释放重入计数次),然后 park;被 signal 后不会直接返回,而是先把自己塞回同步队列尾部重新排队抢锁,抢到才从 await 返回------保证了"await 返回时必然重新持有锁"的强语义。


3. 主线流程剖析 ★★★

3.1 独占获取加锁主线

ReentrantLock.lock()(默认非公平)为例,完整链路:
#mermaid-svg-bdOyjIbTifBf2TiV{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-bdOyjIbTifBf2TiV .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-bdOyjIbTifBf2TiV .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-bdOyjIbTifBf2TiV .error-icon{fill:#552222;}#mermaid-svg-bdOyjIbTifBf2TiV .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-bdOyjIbTifBf2TiV .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-bdOyjIbTifBf2TiV .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-bdOyjIbTifBf2TiV .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-bdOyjIbTifBf2TiV .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-bdOyjIbTifBf2TiV .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-bdOyjIbTifBf2TiV .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-bdOyjIbTifBf2TiV .marker{fill:#333333;stroke:#333333;}#mermaid-svg-bdOyjIbTifBf2TiV .marker.cross{stroke:#333333;}#mermaid-svg-bdOyjIbTifBf2TiV svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-bdOyjIbTifBf2TiV p{margin:0;}#mermaid-svg-bdOyjIbTifBf2TiV .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-bdOyjIbTifBf2TiV .cluster-label text{fill:#333;}#mermaid-svg-bdOyjIbTifBf2TiV .cluster-label span{color:#333;}#mermaid-svg-bdOyjIbTifBf2TiV .cluster-label span p{background-color:transparent;}#mermaid-svg-bdOyjIbTifBf2TiV .label text,#mermaid-svg-bdOyjIbTifBf2TiV span{fill:#333;color:#333;}#mermaid-svg-bdOyjIbTifBf2TiV .node rect,#mermaid-svg-bdOyjIbTifBf2TiV .node circle,#mermaid-svg-bdOyjIbTifBf2TiV .node ellipse,#mermaid-svg-bdOyjIbTifBf2TiV .node polygon,#mermaid-svg-bdOyjIbTifBf2TiV .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-bdOyjIbTifBf2TiV .rough-node .label text,#mermaid-svg-bdOyjIbTifBf2TiV .node .label text,#mermaid-svg-bdOyjIbTifBf2TiV .image-shape .label,#mermaid-svg-bdOyjIbTifBf2TiV .icon-shape .label{text-anchor:middle;}#mermaid-svg-bdOyjIbTifBf2TiV .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-bdOyjIbTifBf2TiV .rough-node .label,#mermaid-svg-bdOyjIbTifBf2TiV .node .label,#mermaid-svg-bdOyjIbTifBf2TiV .image-shape .label,#mermaid-svg-bdOyjIbTifBf2TiV .icon-shape .label{text-align:center;}#mermaid-svg-bdOyjIbTifBf2TiV .node.clickable{cursor:pointer;}#mermaid-svg-bdOyjIbTifBf2TiV .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-bdOyjIbTifBf2TiV .arrowheadPath{fill:#333333;}#mermaid-svg-bdOyjIbTifBf2TiV .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-bdOyjIbTifBf2TiV .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-bdOyjIbTifBf2TiV .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bdOyjIbTifBf2TiV .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-bdOyjIbTifBf2TiV .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bdOyjIbTifBf2TiV .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-bdOyjIbTifBf2TiV .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-bdOyjIbTifBf2TiV .cluster text{fill:#333;}#mermaid-svg-bdOyjIbTifBf2TiV .cluster span{color:#333;}#mermaid-svg-bdOyjIbTifBf2TiV div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-bdOyjIbTifBf2TiV .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-bdOyjIbTifBf2TiV rect.text{fill:none;stroke-width:0;}#mermaid-svg-bdOyjIbTifBf2TiV .icon-shape,#mermaid-svg-bdOyjIbTifBf2TiV .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bdOyjIbTifBf2TiV .icon-shape p,#mermaid-svg-bdOyjIbTifBf2TiV .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-bdOyjIbTifBf2TiV .icon-shape .label rect,#mermaid-svg-bdOyjIbTifBf2TiV .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bdOyjIbTifBf2TiV .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-bdOyjIbTifBf2TiV .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-bdOyjIbTifBf2TiV :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 成功
失败(已被占)
成功
失败


被前驱释放时 unpark 唤醒
线程 T 调用 ReentrantLock.lock()
lock(): CAS state 0→1
✅ 持锁:exclusiveOwnerThread = T

不排队,直接返回
acquire(1) → tryAcquire 再试一次

(含重入判断:owner 是自己则 state+1)
addWaiter: 包装为独占 Node

CAS 尾插同步队列
acquireQueued 循环
前驱是 head

且 tryAcquire 成功?
setHead 自己成为新哨兵

旧 head 脱链 → 返回,持锁
shouldParkAfterFailedAcquire:

把前驱 CAS 标记为 SIGNAL
parkAndCheckInterrupt: park 挂起

逐步解释(对应源码编号):

  1. 快速路径 :非公平锁先无脑 CAS 0→1 抢一次,命中即持锁,不建任何队列;
  2. 慢路径 :CAS 失败 → tryAcquire 中检查是否重入 (owner == 当前线程则 state+1,这也是为什么 lock 必须配 unlock 成对:每层 +1 必须 -1 回去);
  3. 入队:重试也失败 → 生成独占 Node CAS 尾插(空队列先建哨兵 head);
  4. 等锁循环acquireQueued 中线程只关心两件事------我的前驱是不是 head(只有队首候补有资格抢)、抢不到就 park;
  5. 挂牌 SIGNAL 后再 parkshouldParkAfterFailedAcquire 保证 park 前前驱已被标记,杜绝"前驱先释放了才想起没叫我"的窗口;
  6. 唤醒即重赛 :被 unpark 后回到 for(;😉 重新 tryAcquire------被叫醒 ≠ 拿到锁,公平锁下不会输,但非公平锁下可能被新来的线程插队(于是再 park,属于正常竞争)。

3.2 独占释放唤醒主线

#mermaid-svg-Wx8MxxYEAalXL7AP{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Wx8MxxYEAalXL7AP .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Wx8MxxYEAalXL7AP .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Wx8MxxYEAalXL7AP .error-icon{fill:#552222;}#mermaid-svg-Wx8MxxYEAalXL7AP .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Wx8MxxYEAalXL7AP .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Wx8MxxYEAalXL7AP .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Wx8MxxYEAalXL7AP .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Wx8MxxYEAalXL7AP .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Wx8MxxYEAalXL7AP .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Wx8MxxYEAalXL7AP .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Wx8MxxYEAalXL7AP .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Wx8MxxYEAalXL7AP .marker.cross{stroke:#333333;}#mermaid-svg-Wx8MxxYEAalXL7AP svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Wx8MxxYEAalXL7AP p{margin:0;}#mermaid-svg-Wx8MxxYEAalXL7AP .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Wx8MxxYEAalXL7AP .cluster-label text{fill:#333;}#mermaid-svg-Wx8MxxYEAalXL7AP .cluster-label span{color:#333;}#mermaid-svg-Wx8MxxYEAalXL7AP .cluster-label span p{background-color:transparent;}#mermaid-svg-Wx8MxxYEAalXL7AP .label text,#mermaid-svg-Wx8MxxYEAalXL7AP span{fill:#333;color:#333;}#mermaid-svg-Wx8MxxYEAalXL7AP .node rect,#mermaid-svg-Wx8MxxYEAalXL7AP .node circle,#mermaid-svg-Wx8MxxYEAalXL7AP .node ellipse,#mermaid-svg-Wx8MxxYEAalXL7AP .node polygon,#mermaid-svg-Wx8MxxYEAalXL7AP .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Wx8MxxYEAalXL7AP .rough-node .label text,#mermaid-svg-Wx8MxxYEAalXL7AP .node .label text,#mermaid-svg-Wx8MxxYEAalXL7AP .image-shape .label,#mermaid-svg-Wx8MxxYEAalXL7AP .icon-shape .label{text-anchor:middle;}#mermaid-svg-Wx8MxxYEAalXL7AP .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Wx8MxxYEAalXL7AP .rough-node .label,#mermaid-svg-Wx8MxxYEAalXL7AP .node .label,#mermaid-svg-Wx8MxxYEAalXL7AP .image-shape .label,#mermaid-svg-Wx8MxxYEAalXL7AP .icon-shape .label{text-align:center;}#mermaid-svg-Wx8MxxYEAalXL7AP .node.clickable{cursor:pointer;}#mermaid-svg-Wx8MxxYEAalXL7AP .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Wx8MxxYEAalXL7AP .arrowheadPath{fill:#333333;}#mermaid-svg-Wx8MxxYEAalXL7AP .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Wx8MxxYEAalXL7AP .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Wx8MxxYEAalXL7AP .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Wx8MxxYEAalXL7AP .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Wx8MxxYEAalXL7AP .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Wx8MxxYEAalXL7AP .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Wx8MxxYEAalXL7AP .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Wx8MxxYEAalXL7AP .cluster text{fill:#333;}#mermaid-svg-Wx8MxxYEAalXL7AP .cluster span{color:#333;}#mermaid-svg-Wx8MxxYEAalXL7AP div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Wx8MxxYEAalXL7AP .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Wx8MxxYEAalXL7AP rect.text{fill:none;stroke-width:0;}#mermaid-svg-Wx8MxxYEAalXL7AP .icon-shape,#mermaid-svg-Wx8MxxYEAalXL7AP .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Wx8MxxYEAalXL7AP .icon-shape p,#mermaid-svg-Wx8MxxYEAalXL7AP .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Wx8MxxYEAalXL7AP .icon-shape .label rect,#mermaid-svg-Wx8MxxYEAalXL7AP .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Wx8MxxYEAalXL7AP .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Wx8MxxYEAalXL7AP .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Wx8MxxYEAalXL7AP :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} state 未归 0(仍重入)
state 归 0 且 owner 清空
队列空或无人等
有等待者
线程 T 调用 unlock() → release(1)
tryRelease:

state-1,owner==自己校验

归 0 才算真正释放
返回 false,无事发生
释放成功 → head.waitStatus != 0 ?
什么都不做
unparkSuccessor(head)
把 head.waitStatus CAS 清 0
找 head 的下一个有效节点

next 无效/为空 → 从 tail 沿 prev 倒着找

跳过 CANCELLED
LockSupport.unpark(候选线程) → 回到 3.1 第 6 步重赛

java 复制代码
private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    if (ws < 0) compareAndSetWaitStatus(node, ws, 0);   // 清掉 SIGNAL/PROPAGATE

    Node s = node.next;
    if (s == null || s.waitStatus > 0) {   // next 失效(被取消/断链)→ 必须倒着找
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0) s = t;  // 从队尾往前,取最靠近 head 的有效节点
    }
    if (s != null) LockSupport.unpark(s.thread);
}

关键认知 :唤醒目标严格限定"下一个排队的线程 "------这正是公平性的保障:AQS 保证排队顺序即唤醒顺序,插队只能发生在"没排队的线程 vs 正在重赛的队首"之间,已经 park 的队列次序不会被打破。

释放顺序必须是 先改 state、后唤醒(先 tryRelease 后 unparkSuccessor):保证被唤醒者读到的是释放后的新 state,且释放前的写操作对唤醒者可见。

3.3 共享模式获取与传播

共享模式与独占模式的核心差异:一个"成功"可以唤醒一串 。以 CountDownLatch 为例,最后一个 countDown 触发 gate 打开后,所有 await 线程都要被放行。

java 复制代码
// 共享获取:tryAcquireShared 返回 >= 0 表示成功(无需排队)
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;
                    if (interrupted) selfInterrupt();
                    failed = false;
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed) cancelAcquire(node);
    }
}

传播链(setHeadAndPropagate → doReleaseShared):

java 复制代码
private void setHeadAndPropagate(Node node, int propagate) {
    Node h = head;
    setHead(node);                                  // 自己成为哨兵
    // 还有剩余许可,或 head 状态表明有线程在等 → 继续唤醒下一个
    if (propagate > 0 || h == null || h.waitStatus < 0 ||
        (h = head) == null || h.waitStatus < 0) {
        Node s = node.next;
        if (s == null || s.isShared())
            doReleaseShared();                      // 唤醒下一个共享节点,形成链式放行
    }
}

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;  // 抢标记失败就重试
                unparkSuccessor(h);                                          // 唤醒下一个
            } else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                                                    // 避免"信号丢失"竞态
        }
        if (h == head) break;                       // head 没变才退出(唤醒后可能又来新人)
    }
}

PROPAGATE 的来历(JDK 6 的 bug 修复) :曾有一类竞态------线程 A releaseShared 发现 head 状态为 0 直接退出,而新入队的线程 B 恰好把 head 置成 SIGNAL 之前,A 已错过传播窗口,导致 B 永远等不到唤醒。PROPAGATE 把"没有 SIGNAL 可清"的 head 标记为 -3,让后续 setHeadAndPropagate 判断 waitStatus < 0 时仍会继续传播,堵住这个洞。

3.4 Condition 完整链路

await 主流程(以 JDK 8~17 主线为准,细节随版本微调):
锁(state) 同步队列 条件队列(单链表) 等待线程 T1 锁(state) 同步队列 条件队列(单链表) 等待线程 T1 #mermaid-svg-5kkauE80xM6e07Y3{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-5kkauE80xM6e07Y3 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-5kkauE80xM6e07Y3 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-5kkauE80xM6e07Y3 .error-icon{fill:#552222;}#mermaid-svg-5kkauE80xM6e07Y3 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-5kkauE80xM6e07Y3 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-5kkauE80xM6e07Y3 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-5kkauE80xM6e07Y3 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-5kkauE80xM6e07Y3 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-5kkauE80xM6e07Y3 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-5kkauE80xM6e07Y3 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-5kkauE80xM6e07Y3 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-5kkauE80xM6e07Y3 .marker.cross{stroke:#333333;}#mermaid-svg-5kkauE80xM6e07Y3 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-5kkauE80xM6e07Y3 p{margin:0;}#mermaid-svg-5kkauE80xM6e07Y3 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-5kkauE80xM6e07Y3 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-5kkauE80xM6e07Y3 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-5kkauE80xM6e07Y3 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-5kkauE80xM6e07Y3 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-5kkauE80xM6e07Y3 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-5kkauE80xM6e07Y3 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-5kkauE80xM6e07Y3 .sequenceNumber{fill:white;}#mermaid-svg-5kkauE80xM6e07Y3 #sequencenumber{fill:#333;}#mermaid-svg-5kkauE80xM6e07Y3 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-5kkauE80xM6e07Y3 .messageText{fill:#333;stroke:none;}#mermaid-svg-5kkauE80xM6e07Y3 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-5kkauE80xM6e07Y3 .labelText,#mermaid-svg-5kkauE80xM6e07Y3 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-5kkauE80xM6e07Y3 .loopText,#mermaid-svg-5kkauE80xM6e07Y3 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-5kkauE80xM6e07Y3 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-5kkauE80xM6e07Y3 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-5kkauE80xM6e07Y3 .noteText,#mermaid-svg-5kkauE80xM6e07Y3 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-5kkauE80xM6e07Y3 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-5kkauE80xM6e07Y3 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-5kkauE80xM6e07Y3 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-5kkauE80xM6e07Y3 .actorPopupMenu{position:absolute;}#mermaid-svg-5kkauE80xM6e07Y3 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-5kkauE80xM6e07Y3 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-5kkauE80xM6e07Y3 .actor-man circle,#mermaid-svg-5kkauE80xM6e07Y3 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-5kkauE80xM6e07Y3 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 另一线程 signal() await():包装 Node(CONDITION) 挂条件队尾 fullyRelease:释放全部重入次数(记 savedState) while(!isOnSyncQueue) LockSupport.park() transferForSignal:把 T1 从条件队列摘下 enq 塞回同步队列尾,恢复抢锁资格 park 期间被唤醒/重新排队抢锁 acquireQueued(savedState):抢到锁后从 await 返回

java 复制代码
// ConditionObject.await 骨架(JDK 8,逻辑主线与 17 一致)
public final void await() throws InterruptedException {
    if (Thread.interrupted()) throw new InterruptedException();
    Node node = addConditionWaiter();        // ① 挂进条件队列(CONDITION 状态)
    int savedState = fullyRelease(node);     // ② 完全释放锁,记住重入次数
    int interruptMode = 0;
    while (!isOnSyncQueue(node)) {           // ③ 只要还没被挪回同步队列就 park
        LockSupport.park(this);
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) break;
    }
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)  // ④ 重新抢锁
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null) unlinkCancelledWaiters();  // ⑤ 清理已离开的等待者
    if (interruptMode != 0) reportInterruptAfterWait(interruptMode);
}

// signal 只放行队头一个等待者
public final void signal() {
    if (!isHeldExclusively()) throw new IllegalMonitorStateException();  // 必须持锁才能 signal
    Node first = firstWaiter;
    if (first != null) transferForSignal(first);
}

final boolean transferForSignal(Node node) {
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0)) return false;  // 已被取消则放弃
    Node p = enq(node);                    // 塞回同步队列尾
    int ws = p.waitStatus;
    // 前驱已取消,或无法给前驱挂牌 SIGNAL → 直接 unpark,免得它永远睡死
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread);
    return true;
}

理解要点

  1. await 返回时必然重新持有锁 ------这是与 wait/notify 最一致的语义(wait 返回也在 monitor 里);
  2. 条件队列节点被 signal 后先排回同步队列尾,还要和其他线程竞争(非公平锁下可能再等一轮);
  3. 因此业务代码永远要 while (!条件) condition.await(); 而不是 if------被唤醒不等于条件为真(虚假唤醒 + 竞争失败后条件已过期双重原因);
  4. signal 只移一个,signalAll 移全部(循环 transferForSignal);
  5. await 中线程被中断有两种处理模式:THROW_IE(中断即取消等待抛异常)与 REINTERRUPT(拿到锁后再补中断)。

3.5 一次完整的锁竞争时序

场景:线程 A 持锁;B、C 陆续抢锁失败排队;A 释放;B 被唤醒拿锁:
volatile state 同步队列(CLH 变体) 线程 C 线程 B 线程 A volatile state 同步队列(CLH 变体) 线程 C 线程 B 线程 A #mermaid-svg-XF3KK8Xfr283qCyJ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-XF3KK8Xfr283qCyJ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-XF3KK8Xfr283qCyJ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-XF3KK8Xfr283qCyJ .error-icon{fill:#552222;}#mermaid-svg-XF3KK8Xfr283qCyJ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-XF3KK8Xfr283qCyJ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-XF3KK8Xfr283qCyJ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-XF3KK8Xfr283qCyJ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-XF3KK8Xfr283qCyJ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-XF3KK8Xfr283qCyJ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-XF3KK8Xfr283qCyJ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-XF3KK8Xfr283qCyJ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-XF3KK8Xfr283qCyJ .marker.cross{stroke:#333333;}#mermaid-svg-XF3KK8Xfr283qCyJ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-XF3KK8Xfr283qCyJ p{margin:0;}#mermaid-svg-XF3KK8Xfr283qCyJ .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-XF3KK8Xfr283qCyJ text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-XF3KK8Xfr283qCyJ .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-XF3KK8Xfr283qCyJ .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-XF3KK8Xfr283qCyJ .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-XF3KK8Xfr283qCyJ .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-XF3KK8Xfr283qCyJ #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-XF3KK8Xfr283qCyJ .sequenceNumber{fill:white;}#mermaid-svg-XF3KK8Xfr283qCyJ #sequencenumber{fill:#333;}#mermaid-svg-XF3KK8Xfr283qCyJ #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-XF3KK8Xfr283qCyJ .messageText{fill:#333;stroke:none;}#mermaid-svg-XF3KK8Xfr283qCyJ .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-XF3KK8Xfr283qCyJ .labelText,#mermaid-svg-XF3KK8Xfr283qCyJ .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-XF3KK8Xfr283qCyJ .loopText,#mermaid-svg-XF3KK8Xfr283qCyJ .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-XF3KK8Xfr283qCyJ .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-XF3KK8Xfr283qCyJ .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-XF3KK8Xfr283qCyJ .noteText,#mermaid-svg-XF3KK8Xfr283qCyJ .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-XF3KK8Xfr283qCyJ .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-XF3KK8Xfr283qCyJ .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-XF3KK8Xfr283qCyJ .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-XF3KK8Xfr283qCyJ .actorPopupMenu{position:absolute;}#mermaid-svg-XF3KK8Xfr283qCyJ .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-XF3KK8Xfr283qCyJ .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-XF3KK8Xfr283qCyJ .actor-man circle,#mermaid-svg-XF3KK8Xfr283qCyJ line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-XF3KK8Xfr283qCyJ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} lock() CAS 成功 state: 0→1(owner=A) 1 CAS 0→1 失败 2 addWaiter 尾插 node_B(head哨兵 → B) 3 tryAcquire 失败 4 尾插 node_C(head哨兵 → B → C) 5 shouldPark:把 head 挂 SIGNAL 后 park 6 shouldPark:把 B 挂 SIGNAL 后 park(等 B 放行) 7 unlock:state 1→0,owner 清空 8 unparkSuccessor(head):唤醒队首 B 9 醒来自检:前驱 == head 且 CAS state 0→1 成功 10 setHead(B),B 出队成为新哨兵(head → C) 11 继续 park,等 B 释放后再被唤醒 12

要点:唤醒严格按队列序(head.next → 新 head.next......),每个时刻队列里至多一个线程在被唤醒后重赛;出队只是"哨兵前进",无锁化,天然无 ABA 无死锁(所有修改都 CAS + park 语义保证)。


4. 类关系图 ★★★

4.1 JUC 继承全景

渲染错误: Mermaid 渲染失败: Parse error on line 75: ...RWS AQS <|-- TPE["ThreadPoolExecutor ----------------------^ Expecting 'NEWLINE', 'EOF', 'LABEL', got 'SQS'

4.2 AQS 内部结构

#mermaid-svg-WWSS7zLiwNnWlVpl{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-WWSS7zLiwNnWlVpl .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-WWSS7zLiwNnWlVpl .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-WWSS7zLiwNnWlVpl .error-icon{fill:#552222;}#mermaid-svg-WWSS7zLiwNnWlVpl .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-WWSS7zLiwNnWlVpl .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-WWSS7zLiwNnWlVpl .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-WWSS7zLiwNnWlVpl .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-WWSS7zLiwNnWlVpl .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-WWSS7zLiwNnWlVpl .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-WWSS7zLiwNnWlVpl .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-WWSS7zLiwNnWlVpl .marker{fill:#333333;stroke:#333333;}#mermaid-svg-WWSS7zLiwNnWlVpl .marker.cross{stroke:#333333;}#mermaid-svg-WWSS7zLiwNnWlVpl svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-WWSS7zLiwNnWlVpl p{margin:0;}#mermaid-svg-WWSS7zLiwNnWlVpl g.classGroup text{fill:#9370DB;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-WWSS7zLiwNnWlVpl g.classGroup text .title{font-weight:bolder;}#mermaid-svg-WWSS7zLiwNnWlVpl .cluster-label text{fill:#333;}#mermaid-svg-WWSS7zLiwNnWlVpl .cluster-label span{color:#333;}#mermaid-svg-WWSS7zLiwNnWlVpl .cluster-label span p{background-color:transparent;}#mermaid-svg-WWSS7zLiwNnWlVpl .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-WWSS7zLiwNnWlVpl .cluster text{fill:#333;}#mermaid-svg-WWSS7zLiwNnWlVpl .cluster span{color:#333;}#mermaid-svg-WWSS7zLiwNnWlVpl .nodeLabel,#mermaid-svg-WWSS7zLiwNnWlVpl .edgeLabel{color:#131300;}#mermaid-svg-WWSS7zLiwNnWlVpl .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-WWSS7zLiwNnWlVpl .label text{fill:#131300;}#mermaid-svg-WWSS7zLiwNnWlVpl .labelBkg{background:#ECECFF;}#mermaid-svg-WWSS7zLiwNnWlVpl .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-WWSS7zLiwNnWlVpl .classTitle{font-weight:bolder;}#mermaid-svg-WWSS7zLiwNnWlVpl .node rect,#mermaid-svg-WWSS7zLiwNnWlVpl .node circle,#mermaid-svg-WWSS7zLiwNnWlVpl .node ellipse,#mermaid-svg-WWSS7zLiwNnWlVpl .node polygon,#mermaid-svg-WWSS7zLiwNnWlVpl .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-WWSS7zLiwNnWlVpl .divider{stroke:#9370DB;stroke-width:1;}#mermaid-svg-WWSS7zLiwNnWlVpl g.clickable{cursor:pointer;}#mermaid-svg-WWSS7zLiwNnWlVpl g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-WWSS7zLiwNnWlVpl g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-WWSS7zLiwNnWlVpl .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-WWSS7zLiwNnWlVpl .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-WWSS7zLiwNnWlVpl .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-WWSS7zLiwNnWlVpl .dashed-line{stroke-dasharray:3;}#mermaid-svg-WWSS7zLiwNnWlVpl .dotted-line{stroke-dasharray:1 2;}#mermaid-svg-WWSS7zLiwNnWlVpl #compositionStart,#mermaid-svg-WWSS7zLiwNnWlVpl .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-WWSS7zLiwNnWlVpl #compositionEnd,#mermaid-svg-WWSS7zLiwNnWlVpl .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-WWSS7zLiwNnWlVpl #dependencyStart,#mermaid-svg-WWSS7zLiwNnWlVpl .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-WWSS7zLiwNnWlVpl #dependencyStart,#mermaid-svg-WWSS7zLiwNnWlVpl .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-WWSS7zLiwNnWlVpl #extensionStart,#mermaid-svg-WWSS7zLiwNnWlVpl .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-WWSS7zLiwNnWlVpl #extensionEnd,#mermaid-svg-WWSS7zLiwNnWlVpl .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-WWSS7zLiwNnWlVpl #aggregationStart,#mermaid-svg-WWSS7zLiwNnWlVpl .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-WWSS7zLiwNnWlVpl #aggregationEnd,#mermaid-svg-WWSS7zLiwNnWlVpl .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-WWSS7zLiwNnWlVpl #lollipopStart,#mermaid-svg-WWSS7zLiwNnWlVpl .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-WWSS7zLiwNnWlVpl #lollipopEnd,#mermaid-svg-WWSS7zLiwNnWlVpl .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-WWSS7zLiwNnWlVpl .edgeTerminals{font-size:11px;line-height:initial;}#mermaid-svg-WWSS7zLiwNnWlVpl .classTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-WWSS7zLiwNnWlVpl .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-WWSS7zLiwNnWlVpl .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-WWSS7zLiwNnWlVpl :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} head/tail 串起同步队列
firstWaiter/lastWaiter 串起条件队列
Node
-volatile int waitStatus
-volatile Node prev
-volatile Node next
-volatile Thread thread
-Node nextWaiter
+CANCELLED = 1
+SIGNAL = -1
+CONDITION = -2
+PROPAGATE = -3
队列字段
-volatile Node head
-volatile Node tail
+compareAndSetHead()
+compareAndSetTail()
AbstractQueuedSynchronizer
-volatile int state
+getState() / setState()
+compareAndSetState()

4.3 同步队列与条件队列形态

#mermaid-svg-AL27Z8oxmJpe86xX{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-AL27Z8oxmJpe86xX .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-AL27Z8oxmJpe86xX .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-AL27Z8oxmJpe86xX .error-icon{fill:#552222;}#mermaid-svg-AL27Z8oxmJpe86xX .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-AL27Z8oxmJpe86xX .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-AL27Z8oxmJpe86xX .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-AL27Z8oxmJpe86xX .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-AL27Z8oxmJpe86xX .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-AL27Z8oxmJpe86xX .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-AL27Z8oxmJpe86xX .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-AL27Z8oxmJpe86xX .marker{fill:#333333;stroke:#333333;}#mermaid-svg-AL27Z8oxmJpe86xX .marker.cross{stroke:#333333;}#mermaid-svg-AL27Z8oxmJpe86xX svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-AL27Z8oxmJpe86xX p{margin:0;}#mermaid-svg-AL27Z8oxmJpe86xX .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-AL27Z8oxmJpe86xX .cluster-label text{fill:#333;}#mermaid-svg-AL27Z8oxmJpe86xX .cluster-label span{color:#333;}#mermaid-svg-AL27Z8oxmJpe86xX .cluster-label span p{background-color:transparent;}#mermaid-svg-AL27Z8oxmJpe86xX .label text,#mermaid-svg-AL27Z8oxmJpe86xX span{fill:#333;color:#333;}#mermaid-svg-AL27Z8oxmJpe86xX .node rect,#mermaid-svg-AL27Z8oxmJpe86xX .node circle,#mermaid-svg-AL27Z8oxmJpe86xX .node ellipse,#mermaid-svg-AL27Z8oxmJpe86xX .node polygon,#mermaid-svg-AL27Z8oxmJpe86xX .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-AL27Z8oxmJpe86xX .rough-node .label text,#mermaid-svg-AL27Z8oxmJpe86xX .node .label text,#mermaid-svg-AL27Z8oxmJpe86xX .image-shape .label,#mermaid-svg-AL27Z8oxmJpe86xX .icon-shape .label{text-anchor:middle;}#mermaid-svg-AL27Z8oxmJpe86xX .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-AL27Z8oxmJpe86xX .rough-node .label,#mermaid-svg-AL27Z8oxmJpe86xX .node .label,#mermaid-svg-AL27Z8oxmJpe86xX .image-shape .label,#mermaid-svg-AL27Z8oxmJpe86xX .icon-shape .label{text-align:center;}#mermaid-svg-AL27Z8oxmJpe86xX .node.clickable{cursor:pointer;}#mermaid-svg-AL27Z8oxmJpe86xX .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-AL27Z8oxmJpe86xX .arrowheadPath{fill:#333333;}#mermaid-svg-AL27Z8oxmJpe86xX .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-AL27Z8oxmJpe86xX .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-AL27Z8oxmJpe86xX .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-AL27Z8oxmJpe86xX .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-AL27Z8oxmJpe86xX .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-AL27Z8oxmJpe86xX .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-AL27Z8oxmJpe86xX .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-AL27Z8oxmJpe86xX .cluster text{fill:#333;}#mermaid-svg-AL27Z8oxmJpe86xX .cluster span{color:#333;}#mermaid-svg-AL27Z8oxmJpe86xX div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-AL27Z8oxmJpe86xX .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-AL27Z8oxmJpe86xX rect.text{fill:none;stroke-width:0;}#mermaid-svg-AL27Z8oxmJpe86xX .icon-shape,#mermaid-svg-AL27Z8oxmJpe86xX .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-AL27Z8oxmJpe86xX .icon-shape p,#mermaid-svg-AL27Z8oxmJpe86xX .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-AL27Z8oxmJpe86xX .icon-shape .label rect,#mermaid-svg-AL27Z8oxmJpe86xX .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-AL27Z8oxmJpe86xX .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-AL27Z8oxmJpe86xX .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-AL27Z8oxmJpe86xX :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 条件队列(单链表,nextWaiter 串,每条 Condition 一个)
同步队列(双向 CLH 变体,volatile prev/next)
signal 后 transferForSignal

从条件队列摘除 enq 回同步队列尾
head 哨兵

thread=null
Node

thread=B

waitStatus=SIGNAL(挂到前驱上)
Node

thread=C

waitStatus=SIGNAL
Node

thread=D 等待中
tail
Node

thread=E

waitStatus=CONDITION
Node

thread=F

waitStatus=CONDITION

  • 同步队列双向:往前能找前驱(判断 head)、往后能快速唤醒后继,且能应对取消断链;
  • 条件队列单向:只需"从头部摘取"这一个操作;
  • 一个线程同一时刻只会在一条队列里:await 时在条件队列,被 signal 后先回同步队列,抢到锁出队执行。

5. 动手实现一个同步器

AQS 官方文档的经典用法:只需要实现 acquire/release 的"判定逻辑",排队、阻塞、唤醒、取消清理全部白送。

5.1 基于 AQS 的自定义互斥锁

java 复制代码
import java.util.concurrent.locks.AbstractQueuedSynchronizer;

/** 基于 AQS 的互斥锁:不可重入,非公平(官方 javadoc 同款思路) */
public final class MyLock {
    private final Sync sync = new Sync();

    private static final class Sync extends AbstractQueuedSynchronizer {
        @Override protected boolean tryAcquire(int acquires) {
            if (compareAndSetState(0, 1)) {              // state 0→1 成功即持锁
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;                                 // 没抢到 → AQS 帮我们排队
        }
        @Override protected boolean tryRelease(int releases) {
            if (getState() == 0) throw new IllegalMonitorStateException(); // 未持锁释放
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException(); // 谁持有谁释放(无所有权转移)
            setExclusiveOwnerThread(null);
            setState(0);                                  // volatile 写,使之前的写可见
            return true;                                  // 返回 true 才触发唤醒后继
        }
        @Override protected boolean isHeldExclusively() {
            return getExclusiveOwnerThread() == Thread.currentThread();
        }
    }

    public void lock()         { sync.acquire(1); }       // 不可中断版
    public void unlock()       { sync.release(1); }
    public boolean tryLock()   { return sync.tryAcquire(1); }
    public boolean hasQueuedThreads() { return sync.hasQueuedThreads(); }
    public boolean isLocked()  { return sync.isLocked(); }
}

注意 :此锁不可重入 ------若持有线程再次 lock(),tryAcquire 必然 false,线程把自己排进队列去等"自己释放",形成自死锁。这正是 AQS 版本之简洁:重入与否只取决于 tryAcquire 里加不加"owner 是自己就 +1"的代码。

5.2 升级为可重入与公平

java 复制代码
/** 把 tryAcquire 换成下面的逻辑,立即变成可重入 + 可选公平 */
private static final class FairSync extends AbstractQueuedSynchronizer {
    @Override protected boolean tryAcquire(int acquires) {
        final Thread cur = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            // 公平性只加这一行:队列里有更早的等待者就让行
            if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(cur);
                return true;
            }
        } else if (cur == getExclusiveOwnerThread()) {
            int nextc = c + acquires;         // 重入计数 +1
            if (nextc < 0) throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
    @Override protected boolean tryRelease(int releases) {
        int c = getState() - releases;
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        boolean free = (c == 0);              // 计数归 0 才算真释放
        if (free) setExclusiveOwnerThread(null);
        setState(c);
        return free;
    }
}

AQS 的通用套路一目了然 :可重入 = 记录持有者 + 计数加减;公平 = tryAcquire 前先 hasQueuedPredecessors();锁语义全部收敛在这 1~2 个方法里。

5.3 基于 AQS 实现共享闸门 MyLatch

java 复制代码
import java.util.concurrent.locks.AbstractQueuedSynchronizer;

/** 共享模式:CountDownLatch 同款语义的倒计数闸门 */
public final class MyLatch {
    private final Sync sync;

    public MyLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }

    public void await()        { sync.acquireSharedInterruptibly(1); } // 门没开就全部排队
    public boolean await(long millis) throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, millis * 1_000_000L);     // 超时版
    }
    public void countDown()    { sync.releaseShared(1); }              // 每次减 1,归 0 开门
    public long getCount()     { return sync.getCount(); }

    private static final class Sync extends AbstractQueuedSynchronizer {
        Sync(int count) { setState(count); }
        @Override protected int tryAcquireShared(int arg) {
            return getState() == 0 ? 1 : -1;   // 门没开(state>0)→ 负数 → AQS 帮我们排队
        }
        @Override protected boolean tryReleaseShared(int arg) {
            for (;;) {                          // CAS 自旋减一
                int c = getState();
                if (c == 0) return false;       // 已经开门,再减无意义
                int nextc = c - 1;
                if (compareAndSetState(c, nextc)) return nextc == 0;  // 只有"减到 0"那一下返回 true → 唤醒全部
            }
        }
    }
}

共享模式的两个精妙点 :所有 await 线程都排同一个队,但只有最后一个 countDown 者返回 true 触发 releaseShared → doReleaseShared 的链式放行;即使 100 个线程在等,也只需一次传播即可全部唤醒。

5.4 对照与验证 Demo

java 复制代码
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/** 对照验证:MyLock(独占)与 MyLatch(共享)的正确性与自旋阻塞行为 */
public class AqsDemo {
    static final int THREADS = 16;
    static final int PER_THREAD = 50_000;
    static final AtomicInteger counter = new AtomicInteger();

    public static void main(String[] args) throws Exception {
        demoMutex();
        demoUnfairnessObservation();
        demoMyLatch();
    }

    /** ① 独占锁保护计数:结果必须恰好 = THREADS * PER_THREAD */
    static void demoMutex() throws InterruptedException {
        MyLock lock = new MyLock();
        ExecutorService pool = Executors.newFixedThreadPool(THREADS);
        CountDownLatch done = new CountDownLatch(THREADS);
        for (int t = 0; t < THREADS; t++) {
            pool.execute(() -> {
                for (int i = 0; i < PER_THREAD; i++) {
                    lock.lock();
                    try { counter.incrementAndGet(); }   // 临界区:非原子自增,靠锁互斥
                    finally { lock.unlock(); }
                }
                done.countDown();
            });
        }
        done.await();
        pool.shutdown();
        int expect = THREADS * PER_THREAD;
        System.out.println("① MyLock 计数结果 = " + counter.get() + "(期望 " + expect + ")"
                + (counter.get() == expect ? " ✅ 互斥生效" : " ❌ 有丢失"));
    }

    /** ② 观察等待线程真实入队:抢不到锁的线程会阻塞在 AQS 队列里(而非自旋烧 CPU) */
    static void demoUnfairnessObservation() throws Exception {
        MyLock lock = new MyLock();
        lock.lock();                                        // 主线程先占住锁
        AtomicInteger entered = new AtomicInteger();
        ExecutorService pool = Executors.newFixedThreadPool(2);
        for (int i = 0; i < 2; i++) {
            pool.execute(() -> {
                lock.lock();
                entered.incrementAndGet();
                lock.unlock();
            });
        }
        Thread.sleep(100);                                  // 给两个线程时间尝试抢锁
        System.out.println("② 主线程持锁时,队列中等待线程数 = " + lock.hasQueuedThreads()
                + "(1~2 之间,0 则说明被主线程插队吃掉)");
        lock.unlock();                                      // 释放,唤醒队列
        pool.shutdown();
        pool.awaitTermination(1, TimeUnit.SECONDS);
        System.out.println("   释放后进入临界区线程数 = " + entered.get() + " ✅");
    }

    /** ③ MyLatch 闸门:所有子线程 countDown 后,主线程 await 才放行 */
    static void demoMyLatch() throws InterruptedException {
        int n = 8;
        MyLatch latch = new MyLatch(n);
        ExecutorService pool = Executors.newFixedThreadPool(n);
        for (int i = 0; i < n; i++) {
            final int id = i;
            pool.execute(() -> {
                try { Thread.sleep(20 + id * 10); }         // 模拟不同耗时
                catch (InterruptedException ignored) {}
                System.out.println("   worker-" + id + " 完工,countDown");
                latch.countDown();
            });
        }
        latch.await();                                      // 阻塞直到 8 个都 countDown
        System.out.println("③ 8 个 worker 全部完成后主线程才走到这里 ✅");
        pool.shutdown();
    }
}

运行预期输出:

text 复制代码
① MyLock 计数结果 = 800000(期望 800000) ✅ 互斥生效
② 主线程持锁时,队列中等待线程数 = 2(1~2 之间,0 则说明被主线程插队吃掉)
   释放后进入临界区线程数 = 2 ✅
   worker-0 完工,countDown
   ...
③ 8 个 worker 全部完成后主线程才走到这里 ✅

💡 实验建议:把 5.2 的公平版 tryAcquire 换进 MyLock 再跑 ②------hasQueuedThreads 稳定为 2 且无插队;把锁换成 new ReentrantLock(false) 反复跑 ②,偶尔能看到 0(主线程刚释放就被新线程插队),这正是非公平的运行时证据。


6. 扩展点与常见问题 FAQ

6.1 高频面试问题速答

Q1:AQS 为什么用一个 int state 而不是 boolean?

因为同步语义远不止"有/无":重入需要计数(ReentrantLock)、许可需要计数(Semaphore)、两个维度可以按位切分(读写锁高 16/低 16 位)、倒计数门闩需要递减(CountDownLatch)。int + volatile + CAS 是"一个原子变量表达任意同步条件"的最小完备集。

Q2:线程拿到锁失败后,为什么 park 而不是自旋?

自旋只适合临界区极短的纳秒级等待;AQS 的服务对象是"任意长的持锁时间",自旋会空耗 CPU。AQS 只在 CAS 入队/抢锁这类确定极短的窗口自旋,真正的等待一律 LockSupport.park 挂起,释放 CPU。

Q3:为什么唤醒的是 head 的下一个节点,而不是任意一个?

队列就是"先来后到"的秩序本身。若唤醒者任意挑人,公平性无从谈起,且需要在队列中做复杂的随机删除。只唤醒 head.next(严格队首候补),队首抢到后自己出队变为新 head,秩序自然滚动,出队 O(1)。

Q4:为什么 unparkSuccessor 从 tail 往前找,而不是直接找 next?

被取消的节点会把 next 指向自己(断链),且取消时可能正好处于"prev 已连、next 未连"的中间态------next 方向随时可能断;而 prev 方向只要节点还在队列里就必然有效。所以从 tail 沿 prev 逆扫,能保证找到"最靠近 head 的有效节点"。

Q5:非公平锁真的"不公平"吗?会饿死吗?

只是"新来的线程可以和队首重赛一次/多次"。已经 park 的队列次序永远不被打破,所以最多是队首被多插队几次;在极端持续竞争下理论上有饥饿可能,实际中由于新线程也要消耗一次 CAS/调度机会,概率极低。非公平的吞吐优势来自线程亲和性(刚释放锁的线程缓存还是热的,省一次唤醒切换)。

Q6:synchronized 与 AQS(显式锁)怎么选?

维度 synchronized ReentrantLock(AQS)
获取方式 JVM 管程(偏向/轻量/重量级升级) CAS + 队列 + park 自管理
可中断/超时 不支持 lockInterruptibly / tryLock(timeout)
公平 无公平概念 可构造公平锁
多条件等待 每锁 1 个等待集 newCondition() 多个
灵活性/可扩展 不可扩展 继承 AQS 可自定义同步器
崩溃安全 异常自动释放(monitorexit) 必须 finally unlock(JDK17 起有 try-with-resources 风格的 Lock 支持改善)
性能 无竞争时开销极低(偏向锁) 无竞争时 CAS 一次即返回

结论:无竞争或简单互斥用 synchronized(JVM 持续优化);需要超时、可中断、多条件、读多写少(读写锁)、自定义同步语义时用 AQS 家族。JDK 21+ 的虚拟线程下锁竞争模型又有变化,这是另一个话题。

Q7:await 被唤醒后为什么要 while 循环重新检查条件?

三层原因:① 虚假唤醒(spurious wakeup)是 park 的合法行为;② 从 signal 到真正抢到锁之间,别的线程可能又改回了条件(条件在锁内判断,抢到锁才能重新看 );③ await 返回时锁已重新持有,必须重新验证才能安全消费。while (!cond) cond.await() 是铁律。

6.2 源码细节点辨析

D1:为什么 acquire() 中 park 醒来要 Thread.interrupted() 并且最后 selfInterrupt()?

lock() 的语义是"中断不能阻止我拿锁"。park 响应中断会立即返回,若不清除中断位,下次 park 又会立刻返回 → 变成忙等;所以 parkAndCheckInterrupt 用 interrupted()(带清除)取回"是否被中断过"并记下。拿到锁后 selfInterrupt() 把中断标志补回去,让上层代码仍能看到这次中断。

D2:head 为什么要设计成 thread == null 的哨兵?

① 队空判断 O(1):head == tail 即空;② 出队零竞争:旧 head 直接把后继设为新 head,无需在队列里删除节点;③ 避免"队列空但线程刚准备入队"时的 head/tail 双写竞争。代价是多一个虚拟节点。

D3:读锁的"共享重入"怎么实现?CountDownLatch 不需要的东西它为什么需要?

Semaphore/CountDownLatch 的共享计数是全局 的,state 一个 int 足够;但读锁要求"每个线程单独记录自己重入了几次"(释放时只能减自己的)。所以 ReentrantReadWriteLock.Sync 在全局 state>>>16 之外,另用 ThreadLocalHoldCounter(ThreadLocal) 给每个读线程存自己的 HoldCounter(含首次获取标志 firstReader 优化)。这说明:state 表达"全局还剩多少",线程级账本交给 ThreadLocal

D4:写锁获取里为什么 state 高 16 位读锁归零才允许(c != 0 && w == 0 则失败)?

写锁是独占的:一旦有任何读锁持有(state 高位非 0),写锁必须等待------否则写者改数据时读者读到一半。反过来读锁获取时若写锁被独占(低位非 0)也要等。这就是"读写互斥、读读共享"在 state 位运算上的全部表达:c >>> 16 != 0 有读者、c & 0xFFFF != 0 有写者。

D5:acquire 期间 tryAcquire 抛异常会发生什么?

acquireQueued 的 finally 里 failed == true 触发 cancelAcquire:把自己标记 CANCELLED、从队列剔除(尾节点则 CAS 摘 tail,中间节点则让前驱跳过后继),必要时唤醒后继。这套"异常安全"保证 AQS 队列永远不会因为某个同步器子类的 bug 而整队卡死。

D6:FutureTask 为什么在 JDK 8 放弃 AQS?

AQS 是按"获取/释放"语义设计的通用框架,对 FutureTask 而言它的"状态迁移"(NEW→COMPLETING→NORMAL/CANCELLED...)加上 waiters 栈其实只需要一小部分能力;AQS 引入额外的队列维护开销。JDK 8 用自研 volatile int state + Unsafe CAS + Treiber 栈实现等值更精简,也说明模板框架不是零成本的------选型时要考虑"资源争夺型 vs 状态机型"。

6.3 扩展实践方向与总结

还能用 AQS 造什么?(写代码前先想清楚 state 语义即可)

需求 方案
一次性放行的闸门(多个线程等一个事件) 共享模式,state 0/1 门
允许 N 个并发、超出排队(限流器) Semaphore 即此模型
多读单写(缓存场景) ReentrantReadWriteLock 直接可用
等待队列不按 FIFO(如"最短等待时间者先执行") AQS 之外自研(队列顺序是 AQS 固定的)
纯状态机任务(一次性任务、阶段计数) 不一定要 AQS------参考 JDK8 FutureTask 自研 state 机

总结(一句话复习)

AQS = 模板方法(子类决定"能不能") + volatile state(同步条件的载体) + CLH 变体队列("不能"时的排队与唤醒秩序)。

独占模式唤醒严格队首一个;共享模式成功一次传播一串;Condition 是"排队中的排队"------条件队列等 signal,被 signal 后回到同步队列等锁。

想真正吃透它:读一遍 acquire/release/acquireQueued 三个方法,再抄 5.1 的 MyLock 跑一遍多线程,胜过背十篇八股。

学习路径建议:AQS 原理 → ReentrantLock 源码(看两个 Sync 差异)→ ReentrantReadWriteLock(看 state 位拆分与 HoldCounter)→ Semaphore/CountDownLatch(共享传播)→ Condition 源码 → 自研一个同步器验证 → 回到本文对照 FAQ 查漏。配套阅读 JMM(volatile/happens-before)与 LockSupport 内存语义。

相关推荐
LXMXHJ1 小时前
springboot中的线程操作
java·spring boot·后端·线程
敲代码的嘎仔1 小时前
自己设计了一个兑换码算法:自增ID + Base32转码 + 按位加权签名 + 异或混淆,面试被追问细节时终于不用慌了
java·数据库·mysql·算法·微服务·面试·职场和发展
m0_587383002 小时前
外卖CPS系统开发实战:从架构设计到运营落地全指南
java·spring·小程序·架构·需求分析
zhangzeyuaaa2 小时前
Python asyncio 事件循环演进:从手动管理到现代化实践
java·服务器·python
学编程就要猛2 小时前
流式编程及Spring中SSE实现
java·后端·spring·流式编程
随遇而安zx3 小时前
【多线程】---ConcurrentHashMap 源码解析
java·多线程·并发
不才不才不不才3 小时前
Spring 源码系列(27): @Transactional 七大失效场景与源码归因
java·后端·spring
随遇而安zx3 小时前
【多线程】---线程池 ThreadPoolExecutor 原理与调优
java·线程池·多线程
抠脚小弟3 小时前
WebSocketServer 使用详解:从入门到实战
java·spring·websocketserver