AQS(AbstractQueuedSynchronizer)详解

95 阅读5分钟

AQS 是 Java 并发包 (java.util.concurrent.locks) 的核心基础框架,用于构建锁和同步器(如 ReentrantLockSemaphoreCountDownLatch 等)。其核心思想是通过 共享状态(state)队列管理 实现线程的阻塞与唤醒。

一、AQS 核心组件

  1. 共享状态(state)

    • 通过 volatile int state 表示资源状态,具体含义由子类定义。

    • 例如:

      • ReentrantLockstate=0 表示未锁定,state>0 表示锁定次数(可重入)。
      • Semaphorestate 表示可用许可数量。
  2. FIFO 等待队列(CLH 变体)

    • 使用双向链表(Node 类)管理等待线程。

    • 节点模式

      • Node.EXCLUSIVE:独占模式节点(如 ReentrantLock)。
      • Node.SHARED:共享模式节点(如 Semaphore)。

二、AQS 的两种资源共享模式

  1. 独占模式(Exclusive)

    • 资源只能被一个线程持有(如 ReentrantLock)。

    • 关键方法:

      protected boolean tryAcquire(int arg)  // 尝试获取资源(需子类实现)
      protected boolean tryRelease(int arg)  // 尝试释放资源
      
  2. 共享模式(Shared)

    • 资源可被多个线程共享(如 SemaphoreCountDownLatch)。

    • 关键方法:

      protected int tryAcquireShared(int arg)  // 返回剩余可用资源数
      protected boolean tryReleaseShared(int arg)
      

三、AQS 的工作流程

独占模式

  1. 获取资源(如 lock()

    • 调用 tryAcquire 尝试直接获取资源。
    • 若失败,将线程封装为 Node 加入队列尾部,并自旋或阻塞(通过 LockSupport.park())。
    • 前驱节点释放资源时,唤醒后继节点。
  2. 释放资源(如 unlock()

    • 调用 tryRelease 释放资源。
    • 唤醒队列中的第一个有效节点(unparkSuccessor)。

共享模式

1.获取资源(acquireShared)
public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0) // 尝试获取资源
        doAcquireShared(arg);       // 失败则加入队列并阻塞
}
  • 步骤解析

    1. 尝试获取资源:调用 tryAcquireShared(arg),若返回值 ≥0 表示成功,直接访问资源。
    2. 入队阻塞:若失败(返回值 <0),将线程封装为共享模式节点(Node.SHARED),加入队列尾部。
    3. 自旋检查:在队列中自旋检查前驱节点是否为头节点,并尝试再次获取资源。
    4. 阻塞线程:若仍失败,调用 LockSupport.park() 阻塞线程。
2. 资源传播与唤醒
  • 当资源被释放时,AQS 会唤醒队列中的后续节点,并 传播可用资源信号

    • 若后续节点是共享模式,唤醒后会继续尝试获取资源,成功后继续唤醒其后继节点。
    • 这种机制确保多个共享线程可以同时获取资源(如 Semaphore 允许多个线程获取许可)。
3. 释放资源(releaseShared)
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {    // 尝试释放资源
        doReleaseShared();          // 唤醒后续节点并传播
        return true;
    }
    return false;
}
  • 步骤解析

    1. 释放资源:调用 tryReleaseShared(arg) 更新 state
    2. 唤醒传播:调用 doReleaseShared(),唤醒队列中的第一个有效节点,并触发资源传播逻辑。

四、AQS 源码关键逻辑

  1. Node 类

    static final class Node {
        volatile int waitStatus;   // 状态:CANCELLED(1)、SIGNAL(-1)、CONDITION(-2)
        volatile Node prev;        // 前驱节点
        volatile Node next;        // 后继节点
        volatile Thread thread;    // 关联的线程
        Node nextWaiter;           // 条件队列中的下一个节点
    }
    
  2. 获取资源的核心方法 acquire

    public final void acquire(int arg) {
        if (!tryAcquire(arg) && 
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt(); // 恢复中断状态
    }
    ​
    private void doAcquireShared(int arg) {
        final Node node = addWaiter(Node.SHARED);  // 创建共享节点并入队
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor(); // 获取前驱节点
                if (p == head) {                  // 前驱是头节点时尝试获取资源
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {                  // 获取成功
                        setHeadAndPropagate(node, r); // 更新头节点并传播
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                // 检查是否需要阻塞(前驱状态为 SIGNAL)
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())       // 阻塞线程
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);              // 取消获取
        }
    }
    ​
    
  3. 释放资源的核心方法 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;
    }
    private void doReleaseShared() {
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {          // 需要唤醒后继节点
                    if (compareAndSetWaitStatus(h, Node.SIGNAL, 0)) {
                        unparkSuccessor(h);        // 唤醒后继节点
                    }
                } else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) {
                    continue;                      // 确保传播状态
                }
            }
            if (h == head)                        // 头节点未变化则退出循环
                break;
        }
    }
    

五、基于 AQS 实现自定义同步器示例

以下是一个 非可重入的互斥锁 实现:

public class SimpleMutex extends AbstractQueuedSynchronizer {
​
    @Override
    protected boolean tryAcquire(int arg) {
        if (compareAndSetState(0, 1)) { // CAS 设置 state 为 1
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
        return false;
    }
​
    @Override
    protected boolean tryRelease(int arg) {
        if (getState() == 0) throw new IllegalMonitorStateException();
        setExclusiveOwnerThread(null);
        setState(0); // 直接设置 state 为 0(无需 CAS)
        return true;
    }
​
    public void lock() {
        acquire(1);
    }
​
    public void unlock() {
        release(1);
    }
}

六、AQS 的高级特性

  1. 条件变量(Condition)

    • 通过 ConditionObject 实现,内部维护一个条件队列。

    • 使用示例:

      Lock lock = new ReentrantLock();
      Condition condition = lock.newCondition();
      lock.lock();
      try {
          condition.await();      // 释放锁并进入条件队列
          condition.signal();     // 唤醒条件队列中的一个线程
      } finally {
          lock.unlock();
      }
      
  2. 公平锁与非公平锁

    • 公平锁:严格按照队列顺序获取资源。
    • 非公平锁:允许插队(通过直接 CAS 尝试获取资源)。
  3. 可重入性

    • ReentrantLock 通过记录当前持有线程和重入次数实现。

七、常见问题与优化

  1. 性能问题

    • 自旋优化:在进入阻塞前短暂自旋,减少上下文切换。
    • 队列头节点唤醒:避免不必要的唤醒操作。
  2. 中断处理

    • AQS 在 acquire 方法中处理中断,通过 Thread.interrupted() 检查中断状态。
  3. 避免死锁

    • 使用超时机制(如 tryLock(long timeout, TimeUnit unit))。

八、总结

特性描述
核心思想通过共享状态(state)和队列管理实现线程同步。
关键组件stateCLH 队列Node 节点。
资源共享模式独占模式(如锁)、共享模式(如信号量)。
应用场景构建高性能锁、同步工具(如 ReentrantLockSemaphore)。
优点灵活性高,开发者可基于 AQS 快速实现自定义同步器。

通过深入理解 AQS,可以更好地掌握 Java 并发包的底层机制,并设计出高效的线程同步方案。