AQS 深入解析

1. AQS 深入解析

1.1 什么是 AQS

AQS(AbstractQueuedSynchronizer,抽象队列同步器) 是 java.util.concurrent 包的核心基石。

它提供了一套通用的机制来管理同步状态(state) 、阻塞/唤醒线程 、管理等待队列。

JUC 下的各种锁(ReentrantLock、ReentrantReadWriteLock)以及并发工具类(Semaphore、CountDownLatch、CyclicBarrier 等)都是基于 AQS 实现的。


1.1.1 AQS 的核心思想

  1. CLH 同步队列

    • 全称:Craig, Landin, and Hagersten Queue
    • 用来存储被阻塞的线程 ,是一个 FIFO 双向队列。
    • FIFO 公平锁:等待时间最长的线程优先获得锁。
    • 双向队列:支持从两端插入/删除节点(非公平锁可能插队)。
  2. state 属性

    • 用 volatile int state 表示资源状态。
    • 对于 ReentrantLock:
      • state=0 → 没有线程持有锁
      • state>0 → 锁被持有(可重入时 state++)
    • 对于 CountDownLatch:
      • state=0 → 计数器归零,线程可继续执行
      • state>0 → 继续阻塞

1.2 ReentrantLock 底层原理

sequenceDiagram participant Thread1 as 线程1 participant ReentrantLock participant AQS participant CLHQueue as CLH队列 participant Thread2 as 线程2 Thread1->>ReentrantLock: lock() ReentrantLock->>AQS: tryAcquire() AQS-->>Thread1: 成功获取锁 (state=1) Thread2->>ReentrantLock: lock() ReentrantLock->>AQS: tryAcquire() AQS-->>Thread2: 获取失败 AQS->>CLHQueue: 入队等待 CLHQueue-->>Thread2: 阻塞 (park) Thread1->>ReentrantLock: unlock() ReentrantLock->>AQS: tryRelease() AQS->>CLHQueue: signalNext() CLHQueue-->>Thread2: 唤醒 (unpark) Thread2->>AQS: tryAcquire() AQS-->>Thread2: 成功获取锁 (state=1)

1.2.1 公平锁 vs 非公平锁

  • 公平锁:严格按照线程请求的顺序获取锁。
  • 非公平锁:允许在合适的时机插队(比如锁刚释放时,直接抢占)。

1.2.2 以 ReentrantLock 为例

ReentrantLock 内部有 3 个与 AQS 相关的类:

  1. Sync(抽象类,继承 AQS)
  2. NonfairSync(非公平锁实现)
  3. FairSync(公平锁实现)

上锁流程(非公平锁)

1. lock() 方法
java 复制代码
final void lock() {
    if (!initialTryLock()) // 初次尝试获取锁
        acquire(1);        // 获取失败则进入 AQS 获取流程
}

2. NonfairSync.initialTryLock()
java 复制代码
final boolean initialTryLock() {
    Thread current = Thread.currentThread();
    if (compareAndSetState(0, 1)) { // CAS 将 state 从 0 改为 1
        setExclusiveOwnerThread(current);
        return true;
    } else if (getExclusiveOwnerThread() == current) { // 可重入
        setState(getState() + 1);
        return true;
    }
    return false;
}

3. AQS.acquire()
java 复制代码
public final void acquire(int arg) {
    if (!tryAcquire(arg)) // 再次尝试获取锁
        acquire(null, arg, false, false, false, 0L); // 获取失败则进入队列等待
}

4. acquire(null, arg, false, false, false, 0L) 源码
java 复制代码
final void acquire(Node node, int arg,
                   boolean shared, boolean interruptible,
                   boolean timed, long time) {
    Thread current = Thread.currentThread();
    if (node == null)
        node = addWaiter(shared ? Node.SHARED : Node.EXCLUSIVE);

    boolean failed = true;
    try {
        for (;;) { // 自旋
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) { // 轮到自己了,尝试获取锁
                setHead(node); // 成为新的头节点
                p.next = null; // 帮助 GC
                failed = false;
                return;
            }
            // 阻塞当前线程,等待被唤醒
            if (shouldParkAfterFailedAcquire(p, node))
                parkAndCheckInterrupt();
        }
    } finally {
        if (failed)
            cancelAcquire(node); // 获取失败则取消排队
    }
}

简化理解:

  1. 加到等待队列(如果不是头节点)
  2. 自旋等待 ,直到前驱节点是 head
  3. 尝试获取锁 (tryAcquire)
  4. 获取失败 → 阻塞(park)等待唤醒
  5. 获取成功 → 设为新的 head 节点,退出循环

tryAcquire(非公平锁)

java 复制代码
protected final boolean tryAcquire(int acquires) {
    if (getState() == 0 && compareAndSetState(0, acquires)) {
        setExclusiveOwnerThread(Thread.currentThread());
        return true;
    }
    return false;
}

解锁流程

1. AQS.release()
java 复制代码
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        signalNext(head); // 唤醒下一个等待线程
        return true;
    }
    return false;
}
2. tryRelease()
java 复制代码
protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    if (getExclusiveOwnerThread() != Thread.currentThread())
        throw new IllegalMonitorStateException();
    boolean free = (c == 0);
    if (free)
        setExclusiveOwnerThread(null);
    setState(c);
    return free;
}
3. signalNext()
java 复制代码
private static void signalNext(Node h) {
    Node s;
    if (h != null && (s = h.next) != null && s.status != 0) {
        s.getAndUnsetStatus(WAITING);
        LockSupport.unpark(s.waiter);
    }
}

1.3 总结:AQS 的工作原理

  1. state 表示同步状态 (volatile int,CAS 修改)
  2. CLH 队列管理阻塞线程(FIFO)
  3. 获取锁流程 :
    • 尝试直接获取锁(tryAcquire)
    • 失败 → 加入等待队列 → 自旋等待 → 阻塞
  4. 释放锁流程 :
    • 修改 state
    • 唤醒队列中下一个等待的线程

💡 一句话概括 AQS

AQS 就是一个基于 CLH 队列的线程等待/唤醒框架,利用 CAS 操作管理 state,让线程安全地获取和释放资源。

相关推荐
古法老登瞎折腾5 小时前
大数据智能运维实战(十一):运维 MCP 的工程化——权限、限流、审计与错误处理
java·大数据·运维·spring ai·mcp
智能运维指南6 小时前
研制过程“两张皮“怎么破:研制一体化支撑平台的定位、能力边界与落地前提
java·前端·人工智能·嘉为蓝鲸
yz_aiks7 小时前
Spring AI 2.1 ChatMemory 已过时用什么替代:迁移到 Session Memory 实战(JDBC 存储与会话压缩)
java·spring boot·大模型·ai agent·spring ai
金銀銅鐵7 小时前
[Java] 用 GUI 展示 EnumMap 增删键值对时的内部变化
java·后端·ai编程
学长毕设7 小时前
基于SpringBoot的上门洗车服务系统设计与实现
java·spring boot·后端
jason成都7 小时前
RTKLIB-Java 多基线联合RTK:FIX率从0%到91.5%的三层根因修复
java·gnss·rtklib·rtklib_java
鱼宵7 小时前
Stream 终端操作一次讲透:collect、count、reduce 怎么选
java·stream·终端操作
白山编程大哥7 小时前
Java 后端如何优雅地把 List 集合返回给前端
java·前端·list
Linux运维技术栈8 小时前
CVE-2026-91403 高危漏洞修复:Rocky Linux 9 多版本 JDK 环境清理实操
java·linux·运维
On The Road :8 小时前
Java 多线程详解:从入门到线程安全
java·python·安全