AbstractQueuedSynchronizer

AbstractQueuedSynchronizer (AQS) 源码分析

写一个锁,难点从来不在”改个标志位”,而在抢不到时的排队、阻塞、唤醒、取消这套脏活。AQS 把脏活做成模板:框架只管”一个 volatile int state + 一条 CLH 变体的 FIFO 队列”,子类只需用 state 定义”什么算抢到、什么算放掉”(tryAcquire / tryRelease),于是 ReentrantLock、Semaphore、CountDownLatch、ReentrantReadWriteLock 全在它上面长出来。
注意:JDK 15 起 AQS 被整个重写(JDK-8229442),老教程里的 waitStatus / SIGNAL / addWaiter / acquireQueued 在 JDK 17 源码里已经不存在,下面是重写后的真实代码。

// 基于 JDK 17, java.util.concurrent.locks.AbstractQueuedSynchronizer —— 骨架:状态位 + 节点 + state
public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer          // 只提供 exclusiveOwnerThread 字段,记录独占持有者
    implements java.io.Serializable {

    // ...

    // Node status bits, also used as argument and return values
    static final int WAITING   = 1;          // must be 1        // "我要睡了,释放时请叫我",signalNext 只认这一位
    static final int CANCELLED = 0x80000000; // must be negative // 符号位当取消标记,status < 0 一个比较就能判断
    static final int COND      = 2;          // in a condition wait // 节点还在条件队列上,尚未转移到同步队列

    /** CLH Nodes */
    abstract static class Node {
        volatile Node prev;       // initially attached via casTail // 唯一可靠的链:casTail 成功即挂好
        volatile Node next;       // visibly nonnull when signallable // 只是加速用的"提示",可能滞后甚至为 null
        Thread waiter;            // visibly nonnull when enqueued // 非 volatile:可见性搭 casTail/status 这些 volatile 写的顺风车
        volatile int status;      // written by owner, atomic bit ops by others // 自己普通写,别人用原子位运算改

        // ... casPrev / casNext / getAndUnsetStatus / setPrevRelaxed 等 Unsafe 原子操作与字段偏移量省略
    }

    // Concrete classes tagged by type   // 老版用 nextWaiter 字段区分模式,新版直接用类型:instanceof 判断更直白
    static final class ExclusiveNode extends Node { }
    static final class SharedNode extends Node { }

    static final class ConditionNode extends Node
        implements ForkJoinPool.ManagedBlocker { // 实现 ManagedBlocker:在 ForkJoinPool 里 await 不会耗死工作线程
        ConditionNode nextWaiter;            // link to next waiting node // 条件队列是单链表,不用 prev/next
        // ...
    }

    /**
     * Head of the wait queue, lazily initialized.
     */
    private transient volatile Node head;    // 哨兵节点,waiter 为 null;真正排队的从 head.next 开始

    /**
     * Tail of the wait queue. After initialization, modified only via casTail.
     */
    private transient volatile Node tail;    // 入队 = CAS 追加到 tail

    /**
     * @serial The synchronization state.
     */
    private volatile int state;              // 含义由子类定义:ReentrantLock 是重入次数,Semaphore 是剩余许可,CountDownLatch 是计数

    protected final int getState() {
        return state;
    }

    protected final void setState(int newState) {
        state = newState;                    // 普通 volatile 写:只有持锁线程会调(如 tryRelease),无竞争不必 CAS
    }

    protected final boolean compareAndSetState(int expect, int update) {
        return U.compareAndSetInt(this, STATE, expect, update);  // 全家族无锁竞争的核心:抢锁本质是 CAS state
    }
}
// 基于 JDK 17, java.util.concurrent.locks.AbstractQueuedSynchronizer —— 独占获取:一个方法就是整台状态机
public abstract class AbstractQueuedSynchronizer {

    // ReentrantLock.lock() 的入口:先无条件试一次(非公平插队点之一),失败才进大循环
    public final void acquire(int arg) {
        if (!tryAcquire(arg))
            acquire(null, arg, false, false, false, 0L);
    }

    // 子类必须覆写;AQS 不关心"抢到"的语义,只管排队。默认抛异常而非 abstract:允许子类只实现独占或只实现共享
    protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }

    /**
     * Main acquire method, invoked by all exported acquire methods.
     * @return positive if acquired, 0 if timed out, negative if interrupted
     */
    // 老版 addWaiter + acquireQueued + shouldParkAfterFailedAcquire + doAcquireShared 全部合并进这一个方法。
    // 每轮循环只推进一小步(建队列 -> 建节点 -> 入队 -> 标记 WAITING -> park),走到哪一步由当前局部状态决定
    final int acquire(Node node, int arg, boolean shared,
                      boolean interruptible, boolean timed, long time) {
        Thread current = Thread.currentThread();
        byte spins = 0, postSpins = 0;   // retries upon unpark of first thread // 被唤醒后先自旋几次再睡,见下方"原理串讲"
        boolean interrupted = false, first = false;
        Node pred = null;               // predecessor of node when enqueued

        // ... 描述这台状态机十个分支的原文大段注释省略

        for (;;) {
            // 第一段:确认自己是不是"老二"(前驱就是 head)。只有老二有资格抢锁
            if (!first && (pred = (node == null) ? null : node.prev) != null &&
                !(first = (head == pred))) {
                if (pred.status < 0) {
                    cleanQueue();           // predecessor cancelled // 前驱取消了,全队清理一遍再来
                    continue;
                } else if (pred.prev == null) {
                    Thread.onSpinWait();    // ensure serialization // 前驱正在成为 head 的中间态,等它落定
                    continue;
                }
            }
            // 第二段:老二 或 还没入队的新线程,都可以试抢 —— 后者就是非公平"插队"发生的地方
            if (first || pred == null) {
                boolean acquired;
                try {
                    if (shared)
                        acquired = (tryAcquireShared(arg) >= 0);
                    else
                        acquired = tryAcquire(arg);
                } catch (Throwable ex) {
                    cancelAcquire(node, interrupted, false);  // 子类 tryAcquire 抛异常也要把节点摘干净,否则队列永久卡死
                    throw ex;
                }
                if (acquired) {
                    if (first) {
                        node.prev = null;        // 抢到的节点自己变成新哨兵 head
                        head = node;
                        pred.next = null;        // 旧 head 断链,帮 GC
                        node.waiter = null;
                        if (shared)
                            signalNextIfShared(node);  // 共享模式的"传播":我进来了,后面还是共享节点就接着叫醒
                        if (interrupted)
                            current.interrupt(); // 排队期间吃掉的中断在这里补回标志位
                    }
                    return 1;
                }
            }
            // 第三段:抢失败,按状态推进一小步
            Node t;
            if ((t = tail) == null) {           // initialize queue // 队列懒初始化:塞一个哨兵 ExclusiveNode
                if (tryInitializeHead() == null)
                    return acquireOnOOME(shared, arg);  // 连节点都 new 不出来(OOME)就退化成纯自旋,保证锁还能用
            } else if (node == null) {          // allocate; retry before enqueue
                try {
                    node = (shared) ? new SharedNode() : new ExclusiveNode();
                } catch (OutOfMemoryError oome) {
                    return acquireOnOOME(shared, arg);
                }
            } else if (pred == null) {          // try to enqueue
                node.waiter = current;
                node.setPrevRelaxed(t);         // avoid unnecessary fence // 先松弛写 prev,casTail 成功才算真入队
                if (!casTail(t, node))
                    node.setPrevRelaxed(null);  // back out
                else
                    t.next = node;              // next 在 CAS 之后才补,所以 next 天生可能滞后 —— prev 才是真相
            } else if (first && spins != 0) {
                --spins;                        // reduce unfairness on rewaits // 被唤醒又没抢过插队者:先自旋别急着再睡
                Thread.onSpinWait();
            } else if (node.status == 0) {
                node.status = WAITING;          // enable signal and recheck // 先竖"叫我"的牌子,再多循环一轮重试,防丢唤醒
            } else {
                spins = postSpins = (byte)((postSpins << 1) | 1);  // 每被唤醒一次,下次自旋预算翻倍(上限 127)
                try {
                    long nanos;
                    if (!timed)
                        LockSupport.park(this);              // 真正挂起。blocker 传 this,jstack 里能看到在等哪个同步器
                    else if ((nanos = time - System.nanoTime()) > 0L)
                        LockSupport.parkNanos(this, nanos);
                    else
                        break;                               // 超时,掉到下面 cancelAcquire
                } catch (Error | RuntimeException ex) {
                    cancelAcquire(node, interrupted, interruptible); // cancel & rethrow
                    throw ex;
                }
                node.clearStatus();                          // 醒来先清 status,回到循环头重新抢
                if ((interrupted |= Thread.interrupted()) && interruptible)
                    break;                                   // 可中断模式:立刻放弃;不可中断模式:记下来继续抢
            }
        }
        return cancelAcquire(node, interrupted, interruptible);  // 只有中断/超时会走到这
    }

    // 取消 = 打上 CANCELLED 标记 + 触发 cleanQueue 摘链。返回值区分"报告中断"还是"吞掉并补标志"
    private int cancelAcquire(Node node, boolean interrupted,
                              boolean interruptible) {
        if (node != null) {
            node.waiter = null;
            node.status = CANCELLED;
            if (node.prev != null)
                cleanQueue();
        }
        if (interrupted) {
            if (interruptible)
                return CANCELLED;
            else
                Thread.currentThread().interrupt();
        }
        return 0;
    }
}
// 基于 JDK 17, java.util.concurrent.locks.AbstractQueuedSynchronizer —— 释放与唤醒:新版简单到出乎意料
public abstract class AbstractQueuedSynchronizer {

    // ReentrantLock.unlock() 的入口。老版 unparkSuccessor 那套"从尾往前找"没了,就两行
    public final boolean release(int arg) {
        if (tryRelease(arg)) {          // 子类判断是否真正释放(ReentrantLock:state 减到 0)
            signalNext(head);
            return true;
        }
        return false;
    }

    /**
     * Wakes up the successor of given node, if one exists, and unsets its
     * WAITING status to avoid park race. This may fail to wake up an
     * eligible thread when one or more have been cancelled, but
     * cancelAcquire ensures liveness.
     */
    // 只叫 head.next 一个,且只在它竖了 WAITING 牌子时才叫 —— 没牌子说明它还在自旋,不需要 unpark。
    // 允许漏叫(next 滞后/已取消时 s 可能不对),活性由 cleanQueue 里的补偿 signalNext 兜底
    private static void signalNext(Node h) {
        Node s;
        if (h != null && (s = h.next) != null && s.status != 0) {
            s.getAndUnsetStatus(WAITING);   // 原子摘掉 WAITING 位再 unpark,同一个节点不会被重复叫醒
            LockSupport.unpark(s.waiter);
        }
    }

    // ========== 共享模式(Semaphore.acquire / CountDownLatch.await 走这里)==========

    // 与独占共用同一台 acquire 状态机,只是 shared 传 true:成败判据变成 tryAcquireShared(arg) >= 0
    public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            acquire(null, arg, true, false, false, 0L);
    }

    // CountDownLatch.countDown / Semaphore.release 走这里
    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            signalNext(head);       // 只叫醒一个;"唤醒一串"靠 acquire 里的 signalNextIfShared 接力传播
            return true;
        }
        return false;
    }

    /** Wakes up the given node if in shared mode */
    // 传播的另一半:共享节点抢到并成为 head 后,发现下一个还是 SharedNode 就顺手叫醒它。
    // 老版的 PROPAGATE 状态位 + doReleaseShared 循环,被这个"击鼓传花"取代
    private static void signalNextIfShared(Node h) {
        Node s;
        if (h != null && (s = h.next) != null &&
            (s instanceof SharedNode) && s.status != 0) {
            s.getAndUnsetStatus(WAITING);
            LockSupport.unpark(s.waiter);
        }
    }

    // 公平锁的灵魂:队里有没有排在我前面的线程。FairSync.tryAcquire 抢锁前先问它
    public final boolean hasQueuedPredecessors() {
        Thread first = null; Node h, s;
        if ((h = head) != null && ((s = h.next) == null ||
                                   (first = s.waiter) == null ||
                                   s.prev == null))
            first = getFirstQueuedThread(); // retry via getFirstQueuedThread // next 只是提示,读不到就从 tail 沿 prev 重找
        return first != null && first != Thread.currentThread();  // 队首是自己 = 没有前驱(重入/被叫醒的场景)
    }
}

原理串讲

拿默认的非公平 ReentrantLock 走一遍完整链路。线程 B 调 lock(),进 AQS.acquire(1),先无条件 tryAcquire(落到 NonfairSyncnonfairTryAcquire:CAS 把 state 从 0 改 1)。
锁被 A 持着,CAS 失败,进入六参数的 acquire(null, 1, false, false, false, 0L) 大循环。
B 的第一轮:node <mark> nullpred </mark> null,满足 first || pred == null,于是又 tryAcquire 一次——这是第二个插队点,B 此刻还没入队就能抢。
还是失败,往下走:tail == null 就先 tryInitializeHead 塞一个哨兵;下一轮 new ExclusiveNode();再下一轮 casTail 入队;再下一轮把 node.status 写成 WAITING;又转一轮确认还是抢不到,最后 LockSupport.park(this) 挂起。
注意这个节奏:每轮只推进一步,每步之间都夹着一次抢锁机会

为什么 park 前要先写 status = WAITING、然后还要”多转一轮”才睡?这是无锁世界的经典丢唤醒问题:如果 B 检查完”抢不到”之后、park 之前,A 恰好 unlock 了,B 就会睡过去再也没人叫。
AQS 的解法是把”竖牌子”和”睡觉”拆成两轮——竖完 WAITING 后循环回头再抢一次,把这个窗口期的释放兜住;而 signalNext 那边只对 status != 0 的节点 unpark,配合 getAndUnsetStatus(WAITING) 原子摘牌,两边用一个 int 位就完成了握手,不需要任何锁。
就算真的先 unpark 后 park,LockSupport 的许可证语义也保证 park 立即返回。

A 这边 unlock()release(1):tryRelease 把 state 减到 0、清掉 owner,然后 signalNext(head) 只叫醒 head.next 也就是 B。
为什么只叫一个?因为 head 后继以外的节点抢了也白抢(不是老二没资格),全叫醒就是惊群,n 个线程里 n-1 个白跑一趟再睡回去。
B 醒来后 clearStatus,回到循环头,head == pred 判定自己是老二,tryAcquire 成功后执行三连:node.prev = null; head = node; pred.next = null——B 的节点自己变成新哨兵,旧哨兵整个断链等 GC。
所以 head 永远是”当前持有者(或刚离开者)的座位”,队列里不存在单独的 dummy 对象常驻。

为什么 prev 是唯一可信的链,next 只是提示?入队的原子点是 casTail,而 setPrevRelaxed(t) 在 CAS 前、t.next = node 在 CAS 后——CAS 一成功 prev 链就完整,next 链却有一个真空期。
所以所有”必须正确”的操作(cleanQueuegetFirstQueuedThreadhasQueuedPredecessors 的兜底分支)都从 tail 沿 prev 反向走,next 只用来加速唤醒;signalNext 顺着 next 漏叫了也不要紧,cleanQueue 摘取消节点时会补一次 signalNext 保活性。

还有 spins/postSpins 这对不起眼的字段:非公平模式下 B 被叫醒后很可能被一个刚到的线程截胡(人家 CAS 一下就拿走了,B 还在从内核态爬起来)。
如果 B 每次被截胡都立刻重新 park,一次上下文切换几微秒就白烧了。所以 B 每被唤醒一次,postSpins 翻倍,下次先 Thread.onSpinWait() 自旋这么多次再考虑睡——被插队越狠,越倾向于原地等,把”非公平的吞吐优势”和”排队者不至于饿死太惨”折中在几行代码里。

设计取舍

  • 模板方法的极致:AQS 只管排队/阻塞/唤醒,“什么算抢到”全交给子类的 tryAcquire/tryAcquireShared。同一套队列既能做锁(state=重入数)也能做信号量(state=许可数)、闭锁(state=计数),甚至读写锁把一个 int 拆成高 16 位读、低 16 位写。
  • tryAcquire 默认抛 UnsupportedOperationException 而非声明 abstract:子类可以只实现独占(ReentrantLock)或只实现共享(Semaphore),不必为用不到的模式写空方法。
  • 非公平不是 bug 是默认:入队前有两次插队机会(acquire(int) 入口一次、大循环 pred == null 分支一次),换来的是省掉唤醒-接锁的切换延迟,吞吐显著更高;公平锁只是在 tryAcquire 里多问一句 hasQueuedPredecessors
  • 版本大坑:JDK 15 重写后 waitStatusSIGNAL(-1)PROPAGATE(-3)addWaiteracquireQueuedunparkSuccessor 全部不存在了,换成 status 位(WAITING/COND/CANCELLED)和一个合并的 acquire 状态机。
    背旧版八股没问题,但别对着 JDK 17 的源码找 SIGNAL。
  • 名字叫 CLH 变体,实际只借了”节点隐式排队”的思想:真 CLH 是在前驱的状态字段上自旋,AQS 抢不到直接 park 让出 CPU——自旋锁适合极短临界区,通用同步器必须睡。

延伸阅读