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)的基石,其设计平衡了性能、灵活性和可扩展性。

相关推荐
奋进的芋圆3 小时前
DataSyncManager 详解与 Spring Boot 迁移指南
java·spring boot·后端
计算机程序设计小李同学3 小时前
个人数据管理系统
java·vue.js·spring boot·后端·web安全
Echo娴4 小时前
Spring的开发步骤
java·后端·spring
追逐时光者4 小时前
TIOBE 公布 C# 是 2025 年度编程语言
后端·.net
Victor3564 小时前
Hibernate(32)什么是Hibernate的Criteria查询?
后端
Victor3564 小时前
Hibernate(31)Hibernate的原生SQL查询是什么?
后端
_UMR_5 小时前
springboot集成Jasypt实现配置文件启动时自动解密-ENC
java·spring boot·后端
程序员小假5 小时前
我们来说说 Cookie、Session、Token、JWT
java·后端