Java AQS(AbstractQueuedSynchronizer)是 Java 并发包(java.util.concurrent)的核心基石,它通过一个核心变量和两种队列,完美实现了锁机制与线程通信。

D2 Diagram
qtopie.github.io

AQS 核心要素

AQS 的架构设计极其精巧,主要建立在以下三大核心要素之上:


两大核心能力

锁机制(同步队列)

同步队列是一个双向 FIFO 链表,用于管理多线程对资源的排队与竞争:

线程通信(ConditionObject)

ConditionObject 是 AQS 内部实现了 Condition 接口的内建类,用于替代传统的 Object.wait()Object.notify()


协同工作流程

同步队列与等待队列在线程抢锁与条件等待时的完整协同逻辑如下:

D2 Diagram
qtopie.github.io

核心数据结构源码

Node 节点定义

在 JDK 8+ 的 HotSpot 实现中,AQS 内部节点定义如下:

abstract static class Node {
    volatile Node prev;       // 双向同步队列前驱节点
    volatile Node next;       // 双向同步队列后继节点
    Thread waiter;            // 当前处于等待状态的线程
    volatile int status;      // 节点状态 (SIGNAL / CANCELLED / CONDITION / PROPAGATE)
    Node nextWaiter;          // 单向 Condition 等待队列后继节点
}

节点的 status 状态枚举决定了线程在队列中的行为:

CLH 变体双向队列

CLH 队列(Craig, Landin, and Hagersten 队列)是一种基于链表的自旋锁队列,AQS 的同步队列是基于 CLH 队列的改进变体。

特点与改进:


源码实现:获取与释放

AQS 采用模板方法设计模式,子类只需实现 tryAcquire / tryRelease(独占模式)或 tryAcquireShared / tryReleaseShared(共享模式)。

D2 Diagram
qtopie.github.io

独占式获取锁 (acquire)

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

独占式释放锁与头节点唤醒 (release)

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.status != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

总结

  • 虚头节点(head)本身不代表实际线程,仅作为唤醒后继的锚点。
  • 唤醒操作始终优先针对头节点的下一个有效节点,保证了 FIFO 的排队秩序与公平性。

共享模式与传播唤醒 (acquireShared / releaseShared)

在共享模式下(如 Semaphore 释放许可证、CountDownLatch 归零),锁可以同时被多个线程持有。


Condition 条件队列机制

ConditionObject 是 AQS 内部实现的条件等待队列(单向链表):

D2 Diagram
qtopie.github.io

经典应用实现解析

AQS 同步队列模式对比

同步组件AQS 模式state 变量的物理含义核心机制
ReentrantLock独占表示锁的重入次数(0 代表无锁,$N$ 代表重入次数)可重入锁;支持公平/非公平获取策略
Semaphore共享表示可用资源的许可证数量acquire() 递减 state,release() 递增 state
CountDownLatch共享表示未完成的并行任务倒计数countDown() 递减 state,归零时触发 doReleaseShared
ReentrantReadWriteLock混合高 16 位表示读锁重入次数,低 16 位表示写锁重入次数读写分离,支持锁降级

使用 Condition 条件队列的经典类实现

在 Java 官方 SDK(JUC 包)中,使用 ConditionObject(Condition 条件队列)的经典类主要集中在阻塞队列线程池协同场景:

阻塞队列系列(BlockingQueue)与线程池 Worker 协同

在 Java 的 ThreadPoolExecutor 中,核心工作线程(Worker)去任务队列(workQueue)拉取任务时,逻辑并不是“一次只能有一个线程来拉取”,而是:“当队列为空时,所有消费线程都要挂起等待;一旦有新任务入队,再唤醒线程去消费”。底层调用的正是基于 Condition Queue(条件队列) 实现的阻塞队列(如 ArrayBlockingQueueLinkedBlockingQueue)。

核心场景:workQueue.take()

ArrayBlockingQueue 为例,其底层由一把独占锁 ReentrantLock + 一个条件队列 notEmpty(Condition)实现。

线程池中的 Worker 线程获取任务的伪代码逻辑如下:

// ThreadPoolExecutor 内部 Worker 线程获取任务的伪代码逻辑
Runnable getTask() {
    // 调用阻塞队列的 take() 方法拉取任务
    // 如果队列为空,线程就会在这里被挂起(进入 notEmpty 的 Condition Queue)
    Runnable task = workQueue.take(); 
    return task;
}
底层源码逻辑:take()put()

ArrayBlockingQueuetake()(消费拉取)与 put()(生产提交)源码逻辑:

消费线程(Worker)拉取任务:

public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly(); // 1. 先抢锁,保证并发安全
    try {
        // 2. 如果队列为空,不能消费!
        while (count == 0) {
            // 3. 线程进入 notEmpty 条件队列挂起,并释放锁
            notEmpty.await(); 
        }
        // 4. 队列不为空了,出队消费
        return dequeue();
    } finally {
        lock.unlock(); // 5. 释放锁
    }
}

生产者(外部线程)提交任务:

public void put(E e) throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        while (count == items.length) {
            notFull.await(); // 队列满了,生产者挂起
        }
        enqueue(e); // 放入任务
        
        // 【关键点】放入任务后,通知在 notEmpty 条件队列里等待的消费线程
        notEmpty.signal(); 
    } finally {
        lock.unlock();
    }
}
为什么条件等待必须使用 while 而非 if?(虚假唤醒 Spurious Wakeup)

take()put() 源码中,条件检查均使用了 while 循环(如 while (count == 0) notEmpty.await();),而严禁使用 if 条件分支。其核心原因之一正是为了应对 虚假唤醒(Spurious Wakeup) 以及多线程并发竞态。

编程规范与不变式: 无论是使用 AQS 的 Condition.await(),还是底层的 Object.wait() 或 POSIX pthread_cond_wait永远必须在 while 循环中检查等待条件,这是并发编程中绝对的黄金法则:

while (!conditionSatisfied()) {
    condition.await();
}
完整协同过程演示
线程协同总结

线程池与执行器(ThreadPoolExecutor)

Condition 条件队列应用对比表

经典类 / 组件关联的 Condition 数量Condition 队列的物理用途唤醒协同方式
ArrayBlockingQueue2 个(notEmpty, notFull精准区分“队列空”与“队列满”挂起put() 唤醒 notEmptytake() 唤醒 notFull
LinkedBlockingQueue2 个(notEmpty, notFull配合 takeLock / putLock 实现并发读写解耦读写双锁分别管理各自的 Condition 队列
DelayQueue1 个(available队头元素未到期时,消费者超时挂起等待take() 定时挂起;put()/队头变更 signal()
ThreadPoolExecutor1 个(termination外部主线程等待线程池彻底终止彻底 termination 时 signalAll() 唤醒
CyclicBarrier1 个(内部 trip Condition)等待全员到达屏障点时所有线程挂起最后一个线程到达时 trip.signalAll() 解挂全员