AbstractQueuedSynchronizer(AQS)深度分析

117 阅读5分钟

知其然要知其所以然,探索每一个知识点背后的意义,你知道的越多,你不知道的越多,一起学习,一起进步,如果文章感觉对您有用的话,关注、收藏、点赞,有困惑的地方可以评论,我们一起探讨!

AbstractQueuedSynchronizer(AQS)深度分析

1. 概述

AQS 是 Java 并发包(java.util.concurrent.locks)的核心框架,用于构建锁和其他同步器。其核心思想是通过 同步状态(state)CLH 队列变种 管理线程的排队与唤醒,支持 独占模式共享模式


2. 核心组件

(1) 同步状态(state
  • 作用:通过 volatile int 变量表示资源状态,具体语义由子类定义。
    • 示例
      • ReentrantLockstate=0 表示未锁定,state>0 表示重入次数。
      • Semaphorestate 表示可用许可数。
  • 原子操作:通过 CAScompareAndSetState)保证线程安全。
(2) CLH 队列(等待队列)
  • 结构:双向链表(头节点为虚拟节点),节点类型为 Node
  • 节点模式
    • 独占模式(EXCLUSIVE):如 ReentrantLock,同一时刻仅一个线程可持有资源。
    • 共享模式(SHARED):如 Semaphore,多个线程可同时获取资源。
(3) 节点状态(waitStatus
  • CANCELLED (1):线程已取消等待(如超时或中断)。
  • SIGNAL (-1):当前节点的后继节点需要被唤醒。
  • CONDITION (-2):节点处于条件队列(ConditionObject)。
  • PROPAGATE (-3):共享模式下唤醒需传播到后续节点。

3. 核心方法

AQS 采用 模板方法模式,子类需实现以下方法定义同步逻辑:

  • 独占模式
    • tryAcquire(int arg):尝试获取资源。
    • tryRelease(int arg):尝试释放资源。
  • 共享模式
    • tryAcquireShared(int arg):尝试共享式获取资源。
    • tryReleaseShared(int arg):尝试共享式释放资源。
  • 状态查询
    • isHeldExclusively():资源是否被独占。

4. 独占模式流程

(1) 获取资源(acquire
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&      // 尝试获取资源(子类实现)
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 加入队列并阻塞
        selfInterrupt();         // 恢复中断状态
}
  • 步骤
    1. 调用 tryAcquire 尝试获取资源,成功则直接返回。
    2. 失败则创建独占模式节点,通过 addWaiter 加入队列尾部。
    3. 调用 acquireQueued 自旋或阻塞,直到获取资源或被中断。
(2) 释放资源(release
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 释放资源。
    2. 唤醒队列中第一个有效后继节点(unparkSuccessor)。

5. 共享模式流程

(1) 获取资源(acquireShared
public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)  // 尝试共享获取(返回剩余许可数)
        doAcquireShared(arg);       // 加入队列并等待
}
  • 步骤
    1. 调用 tryAcquireShared,返回值 <0 表示资源不足。
    2. 调用 doAcquireShared 加入队列并自旋,直到成功获取或被中断。
(2) 释放资源(releaseShared
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {    // 尝试释放资源
        doReleaseShared();          // 唤醒后续共享节点
        return true;
    }
    return false;
}
  • 步骤
    1. 调用 tryReleaseShared 释放资源。
    2. 调用 doReleaseShared 唤醒后续节点,传播共享模式唤醒。

6. 条件变量(ConditionObject

  • 作用:实现类似 Object.wait()/notify() 的等待/通知机制,但更灵活。
  • 结构:每个 ConditionObject 维护一个单向条件队列。
  • 关键方法
    • await():释放锁,进入条件队列阻塞。
    • signal():将条件队列中的节点转移到同步队列,等待获取锁。
示例:生产者-消费者模型
Lock lock = new ReentrantLock();
Condition notEmpty = lock.newCondition();
Condition notFull = lock.newCondition();

// 生产者
lock.lock();
try {
    while (queue.isFull())
        notFull.await(); // 进入条件队列等待
    queue.put(item);
    notEmpty.signal();  // 唤醒消费者
} finally {
    lock.unlock();
}

7. 关键设计思想

(1) 模板方法模式
  • 分离关注点:AQS 处理队列管理与线程调度,子类定义资源获取/释放逻辑。
  • 复用性:同一套队列机制支持多种同步器(如锁、信号量)。
(2) 无锁队列管理
  • CAS 操作:通过 compareAndSetTailcompareAndSetHead 等实现线程安全的节点入队/出队。
(3) 自旋与阻塞平衡
  • 优化策略:线程在入队后先自旋尝试获取资源,失败后再调用 LockSupport.park() 阻塞,减少上下文切换开销。
(4) 中断与超时处理
  • 响应中断:在 acquireInterruptiblydoAcquireNanos 中处理中断信号和超时逻辑。

8. 性能优化点

  • 头节点优化:头节点为虚拟节点,减少队列操作的竞争。
  • 状态传播:共享模式下唤醒传播(PROPAGATE 状态),提高并发吞吐量。
  • 取消节点清理:跳过已取消的节点(waitStatus=CANCELLED),避免无效遍历。

9. 对比 Synchronized

特性AQSSynchronized
实现方式基于 CAS 和 CLH 队列基于 Monitor(JVM 内置锁)
灵活性高(支持自定义同步策略)低(仅支持内置锁)
可中断性支持不支持
公平性支持公平和非公平模式仅非公平
条件变量支持多个条件队列单个条件队列

10. 典型应用

  • ReentrantLock:可重入独占锁,支持公平/非公平模式。
  • Semaphore:控制并发线程数的信号量。
  • CountDownLatch:等待多个任务完成的同步器。
  • ReentrantReadWriteLock:读写锁,分离读/写操作。

11. 源码关键片段解析

(1) 节点入队(addWaiter
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;
}
(2) 自旋获取资源(acquireQueued
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) { // 前驱是头节点且获取成功
                setHead(node);
                p.next = null; // 帮助 GC
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) && // 检查是否需要阻塞
                parkAndCheckInterrupt())               // 阻塞并检查中断
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node); // 取消获取
    }
}

12. 总结

AQS 通过 同步状态管理CLH 队列 实现了高效的线程同步机制,其设计体现了以下核心思想:

  • 模板方法模式:分离通用逻辑与具体实现。
  • 无锁化操作:通过 CAS 减少锁竞争。
  • 灵活扩展性:支持独占、共享模式及条件变量。
  • 性能优化:自旋尝试、头节点优化、状态传播等。