前言

大家好,我是程序员梁白开,今天我们聊一聊AQS(抽象队列同步器)。

在 Java 并发编程领域,有一个 “隐形王者” 始终支撑着 ReentrantLock、CountDownLatch、Semaphore 等核心工具的运行 —— 它就是 AbstractQueuedSynchronizer(简称 AQS)。作为 JUC 包的底层骨架,AQS 通过 “状态控制 + 队列管理” 的巧妙设计,让开发者无需关注复杂的线程排队、阻塞与唤醒逻辑,只需几行代码就能实现自定义同步器。本文将从核心原理、工作机制到实战应用,带你彻底搞懂 AQS 的底层逻辑。


一、AQS 是什么?一句话看透核心定位

AQS 是 Java.util.concurrent.locks 包下的抽象类,本质是构建锁和同步器的通用框架。它的核心设计思想可以概括为两点:

  • 状态控制:用 volatile 修饰的 int 变量(state)存储同步状态,支持原子操作
  • 队列管理:通过 FIFO 双向队列(CLH 队列变体)管理等待线程

简单说,AQS 就像一个 “并发工具箱”,封装了线程排队、阻塞唤醒等共性逻辑,而将 “是否允许获取资源” 的判断逻辑(如锁的获取条件)留给子类实现。这种设计让 JUC 中的各类同步组件得以快速落地,也让自定义同步器变得简单高效。

二、AQS 核心结构:状态与队列的协作之道

AQS 的核心能力源于两大组件的协同工作:同步状态(state)和同步队列(CLH 队列),再加上条件队列的补充,构成了完整的同步体系。

2.1 同步状态(state):AQS 的 “灵魂变量”

state 是 AQS 中最关键的成员变量,被 volatile 修饰保证可见性,通过 CAS 操作保证原子性修改:

private volatile int state;
// 原子更新状态的核心方法
protected final boolean compareAndSetState(int expect, int update) {
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

state 的含义由子类自由定义,极具灵活性:

  • ReentrantLock 中:state 表示锁的重入次数(0 = 空闲,≥1 = 被占用)
  • Semaphore 中:state 表示可用许可数量
  • CountDownLatch 中:state 表示倒计时计数器初始值

2.2 同步队列(CLH 队列):线程的 “等候大厅”

当线程尝试获取资源失败时,AQS 会将其封装为 Node 节点,加入一个 FIFO 双向链表(CLH 队列变体)中等待。

2.2.1 Node 节点核心结构

每个节点包含线程引用、等待状态、前后指针等关键信息:

static final class Node {
    volatile int waitStatus; // 节点状态(CANCELLED/SIGNAL等)
    volatile Node prev;      // 前驱节点
    volatile Node next;      // 后继节点
    volatile Thread thread;  // 关联线程
    Node nextWaiter;         // 条件队列后继节点
}

节点状态(waitStatus)的核心取值:

  • CANCELLED (1):线程因超时 / 中断取消等待,变为无效节点
  • SIGNAL (-1):后继节点需要被唤醒,当前节点释放资源时需触发唤醒
  • CONDITION (-2):节点处于条件队列中
  • PROPAGATE (-3):共享模式下状态需向后传播

2.2.2 队列的核心特性

  • 队列由 head(头节点)和 tail(尾节点)指针维护
  • 头节点是 “哨兵节点”,不关联线程,代表当前持有资源的线程
  • 新节点通过 CAS 操作加入队尾,保证线程安全
  • 双向链表设计让节点取消(如超时)时,能通过 prev/next 指针快速移除,时间复杂度 O (1)(单向链表需 O (n))

2.3 条件队列:线程协作的 “专属通道”

AQS 的内部类 ConditionObject 实现了 Condition 接口,提供 await ()/signal () 机制,对应独立的条件队列:

  • 一个 AQS 实例可关联多个条件队列,满足多场景线程协作(如生产者 - 消费者模型)
  • 调用 await () 时,线程会释放锁并加入条件队列;调用 signal () 时,节点会被转移到同步队列等待获取锁
  • 相比 Object.wait (),ConditionObject 支持精确唤醒、多条件队列,灵活性更高

三、AQS 核心工作机制:两种模式 + 模板方法

AQS 支持两种资源获取模式,通过模板方法模式定义同步逻辑骨架,子类只需重写关键方法即可。

3.1 两种核心模式:独占与共享

独占模式(Exclusive)

  • 特性:同一时间仅一个线程能获取资源
  • 典型实现:ReentrantLock
  • 核心方法:
  • acquire (int arg):独占式获取资源,忽略中断
  • release (int arg):独占式释放资源,唤醒后继节点

共享模式(Shared)

  • 特性:多个线程可同时获取资源
  • 典型实现:Semaphore、CountDownLatch
  • 核心方法:
    • acquireShared (int arg):共享式获取资源,返回≥0 表示成功
    • releaseShared (int arg):共享式释放资源,支持 “唤醒传播”(唤醒一个节点后,该节点继续唤醒后续节点)

3.2 模板方法模式:AQS 的设计精髓

AQS 定义了同步操作的骨架(模板方法),子类需重写以下 protected 方法(默认抛出异常):

方法 功能描述 适用模式
tryAcquire(int arg) 独占式获取资源,成功返回 true 独占
tryRelease(int arg) 独占式获取资源,成功返回 true 独占
tryAcquireShared(int arg) 共享式获取资源,返回≥0 表示成功 共享
tryReleaseShared(int arg) 共享式释放资源,成功返回 true 共享
isHeldExclusively() 判断当前线程是否独占资源 独占

模板方法(如 acquire、release)会调用这些子类实现的方法,完成 “尝试获取 - 排队 - 阻塞 - 唤醒” 的完整流程,极大简化了同步器的实现。

四、高频面试点:AQS 关键问题解析

4.1 公平锁与非公平锁如何实现?

  • 非公平锁:线程直接 CAS 抢占资源,无需检查队列(ReentrantLock 默认)
protected final boolean tryAcquire(int acquires) {
    if (compareAndSetState(0, 1)) { /* 直接抢锁 */ }
}
  • 公平锁:抢占前先调用 hasQueuedPredecessors () 检查队列是否有等待线程
protected final boolean tryAcquire(int acquires) {
    if (hasQueuedPredecessors()) return false; // 先查队列
    if (compareAndSetState(0, 1)) { /* 抢锁 */ }
}
  • 权衡:非公平锁吞吐量高(减少线程切换),但可能导致线程饥饿;公平锁避免饥饿,但上下文切换频繁。

4.2 线程中断如何处理?

  • 不可中断模式(acquire ()):线程被中断后继续等待,获取资源后补设中断标记
  • 可中断模式(acquireInterruptibly ()):线程被中断时直接抛出 InterruptedException

4.3 ConditionObject 与 Object.wait () 的区别?

对比维度 ConditionObject Object.wait()
绑定对象 仅绑定 AQS 实现的锁 任意 synchronized 对象
队列数量 支持多个条件队列 仅一个等待队列
唤醒精度 signal () 精确唤醒指定队列线程 notify () 随机唤醒一个线程
中断支持 提供 awaitUninterruptibly () 需手动处理中断

五、实战:基于 AQS 实现自定义独占锁

理解 AQS 的最佳方式是动手实践,下面实现一个简单的可重入独占锁:

import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Lock;

public class MyReentrantLock implements Lock {
    // 内部AQS子类,定义state语义和获取/释放逻辑
    private final Sync sync = new Sync();

    private static class Sync extends AbstractQueuedSynchronizer {
        // 尝试获取独占锁(支持重入)
        @Override
        protected boolean tryAcquire(int arg) {
            Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) { // 锁空闲,CAS抢占
                if (compareAndSetState(0, arg)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            } else if (current == getExclusiveOwnerThread()) { // 重入,递增state
                setState(c + arg);
                return true;
            }
            return false; // 其他线程持有锁,获取失败
        }

        // 尝试释放独占锁
        @Override
        protected boolean tryRelease(int arg) {
            if (Thread.currentThread() != getExclusiveOwnerThread()) {
                throw new IllegalMonitorStateException();
            }
            int c = getState() - arg;
            boolean free = (c == 0);
            if (free) {
                setExclusiveOwnerThread(null); // 完全释放,清空持有线程
            }
            setState(c);
            return free;
        }

        // 判断当前线程是否独占锁
        @Override
        protected boolean isHeldExclusively() {
            return getExclusiveOwnerThread() == Thread.currentThread();
        }
    }

    // Lock接口实现(调用AQS模板方法)
    @Override
    public void lock() { sync.acquire(1); }

    @Override
    public void unlock() { sync.release(1); }

    // 其他Lock接口方法(lockInterruptibly、tryLock等)省略...
}

核心逻辑:通过重写 tryAcquire 和 tryRelease 定义 state 的操作规则,AQS 自动处理线程排队、阻塞与唤醒,仅需几十行代码就实现了可重入锁的核心功能。

六、AQS 的应用场景与最佳实践

6.1 适用场景

  • 实现复杂同步器:需要自定义锁逻辑(如可中断锁、超时锁、读写锁)
  • 资源限流:用 Semaphore 控制并发访问数(如数据库连接池)
  • 线程协作:用 CountDownLatch 实现主线程等待多子线程完成
  • 读多写少场景:用 ReentrantReadWriteLock 提高并发效率

6.2 不适用场景

  • 简单同步需求:直接用 synchronized(JDK1.6 后优化,性能接近 ReentrantLock,无需手动释放)
  • 无等待队列需求:用 AtomicInteger 等原子类(基于 CAS,无队列开销)
  • 分布式场景:AQS 是进程内同步,跨 JVM 需用 Redis/ZooKeeper 实现分布式锁

总结

AQS 是 Java 并发编程的 “底层密码”,其 “状态控制 + 队列管理” 的设计思想、模板方法模式的应用,让复杂同步逻辑的实现变得标准化。理解 AQS 不仅能看透 JUC 组件的底层原理,更能让你在面对复杂并发场景时,快速构建自定义同步器。

从 ReentrantLock 的公平 / 非公平锁,到 Semaphore 的许可机制,再到 CountDownLatch 的倒计时功能,AQS 的影子无处不在。掌握它,你将在并发编程的世界中更具深度和竞争力。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐