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,让线程安全地获取和释放资源。

相关推荐
imDwAaY5 分钟前
Bean的生命周期
java·笔记·后端·学习·spring·dubbo
不停喝水6 分钟前
【前端转全栈java速通课】 项目实战④-1 Spring Boot 创建项目-定义接口-请求参数处理-分层规范-依赖注入
java·前端·spring boot
上下求索,莫负韶华13 分钟前
Spring全家桶
java·后端·spring
w_zero_one26 分钟前
链表(2)
java·数据结构·链表
白杨尚青33 分钟前
C++入门篇(十一):string(中)——容量与增删改查:一篇吃透所有常用接口(万字详解)
java·开发语言·c++
SimonKing43 分钟前
SSE、WebSocket 连接丢 Redis 里?那可踩大坑了!
java·后端·程序员
倔强的石头1061 小时前
【Linux指南】动静态库系列(九):动态库如何进入进程地址空间:从磁盘 .so 到共享内存映射
java·linux·服务器
TeamDev1 小时前
JxBrowser 9.5.2 版本发布啦!
java·前端·windows·chromium·混合应用·jxbrowser·浏览器控件
鬼手点金1 小时前
Claude Code示范案例-进阶学习路径
java·服务器·前端·学习·计算机视觉·前向传播
浪子明X1 小时前
用 CMake Presets 固定跨平台构建:从本地开发到 CI 的依赖一致性实践
java·spring·ci/cd