Java AQS(AbstractQueuedSynchronizer)实现原理详解

AQS 是 Java 并发包中构建锁和同步器的核心框架,其通过 双向队列管理线程等待状态 和 共享资源状态(state) 的原子操作,实现高效的线程同步。以下是其实现原理的分步解析:

1. 核心数据结构与状态管理

  • state 变量
    由 volatile 修饰的整型变量,表示共享资源的状态,如锁的重入次数(ReentrantLock)、信号量许可数(Semaphore)。
arduino 复制代码
private volatile int state; // 核心状态变量

同步队列(CLH 变种)

双向链表结构,节点为 Node 类型,保存等待线程及状态:

arduino 复制代码
static final class Node {
  volatile int waitStatus;  // 节点状态(CANCELLED/SIGNAL/CONDITION/PROPAGATE)
    volatile Node prev;       // 前驱节点
    volatile Node next;       // 后继节点
    volatile Thread thread;   // 绑定的线程
    Node nextWaiter;          // 条件队列或共享模式标记
}

2. 资源获取与释放流程

2.1 独占模式(如 ReentrantLock)

  • 获取资源(acquire)
scss 复制代码
public final void acquire(int arg) {
   if (!tryAcquire(arg) &&       // 子类实现:尝试直接获取资源
       acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 失败则入队并阻塞
       selfInterrupt();          // 恢复中断状态
}
  1. tryAcquire:子类自定义资源获取逻辑(如 CAS 修改 state)。
  2. addWaiter :将当前线程封装为 Node.EXCLUSIVE 节点,通过 CAS 加入队列尾部。
  3. acquireQueued:自旋或阻塞等待,直到被前驱节点唤醒并成功获取资源。

释放资源(release)

java 复制代码
public final boolean release(int arg) {
    if (tryRelease(arg)) {        // 子类实现:释放资源
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);    // 唤醒后继节点
        return true;
    }
    return false;
}
  1. tryRelease:子类定义资源释放逻辑(如减少 state)。
  2. unparkSuccessor:唤醒队列中第一个有效节点的线程。

2.2 共享模式(如 Semaphore)

  • 获取资源(acquireShared)

    arduino 复制代码
    public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0) // 子类实现:尝试获取共享资源
            doAcquireShared(arg);      // 入队并等待
    }
  1. tryAcquireShared:返回剩余可用资源数,负值表示失败。
  2. doAcquireShared:类似独占模式,但成功获取后会传播唤醒后续共享节点。
  • 释放资源(releaseShared)

    arduino 复制代码
    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {   // 子类实现:释放资源
            doReleaseShared();         // 唤醒后续节点并传播
            return true;
        }
        return false;
    }

3. 同步队列操作细节

  • 入队(addWaiter)
    通过 CAS 自旋确保线程安全地插入尾节点:
ini 复制代码
private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) { // CAS 更新尾节点
            pred.next = node;
            return node;
        }
    }
    enq(node); // 队列为空或 CAS 失败时,循环入队
    return node;
}

阻塞与唤醒(LockSupport)

  • 挂起线程 :LockSupport.park(this) 在 acquireQueued 中调用。
  • 唤醒线程 :LockSupport.unpark(thread) 在 unparkSuccessor 中触发

4. 条件变量(ConditionObject)实现

  • 条件队列
    单向链表维护等待条件的线程,节点通过 nextWaiter 链接。
java 复制代码
public class ConditionObject implements Condition {
   private transient Node firstWaiter; // 条件队列头
   private transient Node lastWaiter;  // 条件队列尾
}
  • await() 流程

    1. 释放当前线程持有的锁(修改 state)。
    2. 将线程加入条件队列。
    3. 阻塞直到被 signal() 或中断。
    4. 重新竞争锁。
  • signal() 流程

    1. 将条件队列中的节点转移到同步队列。
    2. 修改节点状态为 SIGNAL,等待被唤醒。

5. 关键状态与 CAS 操作

  • waitStatus 状态

    • CANCELLED (1) :节点因超时或中断被取消。
    • SIGNAL (-1) :后继节点需要被唤醒。
    • CONDITION (-2) :节点在条件队列中。
    • PROPAGATE (-3) :共享模式下唤醒需传播。
kotlin 复制代码
// 更新尾节点
private final boolean compareAndSetTail(Node expect, Node update) {
    return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}

6. 公平与非公平锁实现

  • 非公平锁

    线程直接尝试获取锁(插队),减少上下文切换:

    arduino 复制代码
    final boolean nonfairTryAcquire(int acquires) {
        // 直接尝试 CAS 修改 state,不检查队列
    }
  • 公平锁

    检查同步队列是否有等待节点,避免插队:

    arduino 复制代码
    protected final boolean tryAcquire(int acquires) {
        if (hasQueuedPredecessors()) // 检查队列是否为空
            return false;
        // CAS 修改 state
    }

7. 总结:AQS 核心设计优势

设计要点 实现方式 优势
状态管理 volatile state + CAS 原子操作 高效、无锁的并发控制
队列调度 双向同步队列 + 条件队列 灵活支持独占/共享模式及条件变量
模板方法模式 子类实现 tryAcquire/tryRelease 高度可扩展性
线程阻塞与唤醒 LockSupport.park()/unpark() 精准控制线程状态,避免忙等待

通过上述机制,AQS 成为 Java 并发包中高效同步器(如 ReentrantLock、CountDownLatch、Semaphore)的基石,其设计平衡了性能、灵活性和可扩展性。

相关推荐
IT_陈寒13 分钟前
JavaScript实战技巧总结:深挖数组遍历的性能陷阱与实战解法
前端·人工智能·后端
学长毕设10 小时前
基于springboot的食品仓库管理系统
java·spring boot·后端
滴滴答答哒10 小时前
ASP.NET Core + FaceRecognitionDotNet 人脸识别实战:DI 单例、特征向量持久化与 1:N 比对
后端·asp.net
captain37613 小时前
MyBatis操作数据库(入门2)
java·数据库·spring boot·后端·spring·mybatis
IT_陈寒14 小时前
Java序列化踩坑记:对象循环引用让我加班两小时
前端·人工智能·后端
泡海椒14 小时前
05-雷达图多维画像-信用评级多维画像
后端
王中阳Go14 小时前
5.27 分钟干完后端和全部测试:一场三小时笔试的五个失分点,四个不在代码上
大数据·数据库·人工智能·后端·大语言模型
大哥430914 小时前
Modbus 的"字序":一个配错就变成天文数字,或者干脆变成 0
后端
岩钧14 小时前
New API 源码解析:一个生产级 AI 网关的架构与实现
后端
学到老活到老15 小时前
第 24 集:配置文件写了 80,Spring Boot 为什么读到 90?
java·后端·spring