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 并发包中高效同步器(如 ReentrantLockCountDownLatchSemaphore)的基石,其设计平衡了性能、灵活性和可扩展性。

相关推荐
日月星辰Ace1 小时前
jwk-set-uri
java·后端
用户108386386801 小时前
95%开发者不知道的调试黑科技:Apipost让WebSocket开发效率翻倍的秘密
前端·后端
疏狂难除1 小时前
基于Rye的Django项目通过Pyinstaller用Github工作流简单打包
后端·python·django
钢板兽1 小时前
Java后端高频面经——JVM、Linux、Git、Docker
java·linux·jvm·git·后端·docker·面试
未完结小说2 小时前
声明式远程调用:OpenFeign 基础教程
后端
MickeyCV2 小时前
《苍穹外卖》SpringBoot后端开发项目重点知识整理(DAY1 to DAY3)
java·spring boot·后端·苍穹外卖
uhakadotcom2 小时前
ClickHouse入门:快速掌握高性能数据分析
后端·面试·github
雷渊2 小时前
深入分析mysql中的binlog和redo log
java·后端·面试
uhakadotcom2 小时前
Pydantic Extra Types:扩展数据类型的强大工具
后端·面试·github
uhakadotcom2 小时前
Spring Fu:让Spring Boot启动提速40%的黑科技
后端·面试·github