10-Java并发编程基石:AQS(AbstractQueuedSynchronizer)深度解析

5 阅读4分钟

Java并发编程基石:AQS(AbstractQueuedSynchronizer)深度解析

一、AQS在并发体系中的核心地位

1. JDK并发工具依赖关系

graph TD
    A[AQS] --> B[ReentrantLock]
    A --> C[CountDownLatch]
    A --> D[Semaphore]
    A --> E[ThreadPoolExecutor.Worker]
    A --> F[ReentrantReadWriteLock]

2. AQS核心设计思想

设计理念实现方式优势
模板方法模式提供tryAcquire/tryRelease等钩子方法支持自定义同步器
CLH队列变种通过Node节点实现线程排队公平性保障+低竞争开销
状态机管理volatile int state+CAS操作高效的状态变更

二、AQS核心数据结构解析

1. 关键字段源码

// 同步状态(子类通过CAS修改)
private volatile int state;

// CLH队列头尾指针
private transient volatile Node head;
private transient volatile Node tail;

// Node节点结构
static final class Node {
    volatile int waitStatus;  // 取值:CANCELLED/SIGNAL/CONDITION/PROPAGATE
    volatile Node prev;
    volatile Node next;
    volatile Thread thread;
    Node nextWaiter;  // 条件队列专用
}

2. 状态流转图

stateDiagram
    [*] --> INIT
    INIT --> CANCELLED: 线程中断/超时
    INIT --> SIGNAL: 前驱节点设置
    INIT --> CONDITION: 进入条件队列
    SIGNAL --> BLOCKED: 进入阻塞状态
    BLOCKED --> ACQUIRED: 获取锁成功

三、独占模式源码剖析(以ReentrantLock为例)

1. 获取锁流程

public final void acquire(int arg) {
    if (!tryAcquire(arg) && // 子类实现
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

final boolean acquireQueued(final Node node, int arg) {
    boolean interrupted = false;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) { // 只有前驱是头节点才能尝试获取
                setHead(node);
                p.next = null; // help GC
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node)) // 检查是否需要阻塞
                interrupted |= parkAndCheckInterrupt(); // 调用LockSupport.park
        }
    } catch (Throwable t) {
        cancelAcquire(node); // 异常处理
        throw t;
    }
}

2. 释放锁流程

public final boolean release(int arg) {
    if (tryRelease(arg)) { // 子类实现
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h); // 唤醒后继节点
        return true;
    }
    return false;
}

四、共享模式实现(以CountDownLatch为例)

1. 关键差异点

模式共享标识传播机制典型应用
独占模式Node.EXCLUSIVE不传播ReentrantLock
共享模式Node.SHARED通过doReleaseShared传播CountDownLatch/Semaphore

2. 共享锁获取示例

public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0) // 返回值<0表示获取失败
        doAcquireShared(arg);
}

private void doAcquireShared(int arg) {
    final Node node = addWaiter(Node.SHARED);
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r); // 关键:设置头节点并传播
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node))
                parkAndCheckInterrupt();
        }
    } catch (Throwable t) {
        cancelAcquire(node);
        throw t;
    }
}

五、条件队列实现原理

1. await/signal流程

// await() 核心逻辑
public final void await() throws InterruptedException {
    Node node = addConditionWaiter(); // 加入条件队列
    int savedState = fullyRelease(node); // 完全释放锁
    while (!isOnSyncQueue(node)) {
        LockSupport.park(this); // 阻塞
        if (Thread.interrupted())
            throw new InterruptedException();
    }
    acquireQueued(node, savedState); // 重新竞争锁
}

// signal() 核心逻辑
private void doSignal(Node first) {
    do {
        if ((firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;
    } while (!transferForSignal(first) && // 转移到同步队列
             (first = firstWaiter) != null);
}

2. 与synchronized条件队列对比

维度AQS条件队列synchronized wait/notify
队列数量支持多个条件队列单个等待队列
灵活性可中断/超时等待仅基础等待/通知
性能更细粒度的控制JVM内置优化

六、AQS性能优化实践

1. 避免CLH队列竞争技巧

  • 快速路径尝试:先直接CAS获取状态(如ReentrantLock的非公平模式)
  • 延迟初始化:只有竞争时才构建队列(head/tail的懒加载)

2. 状态设计建议

// 正确示例:读写锁状态拆分
static final int SHARED_SHIFT   = 16;
static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;

// 读锁占用高16位,写锁占用低16位
static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

七、常见问题QA

💬 Q1:为什么AQS采用CLH队列而非MCS队列?

答案

  1. 前驱节点监听:CLH通过前驱节点的waitStatus减少自旋
  2. 无锁入队:CAS操作只需修改tail指针
  3. 内存效率:节点只需保存后继指针(MCS需保存前驱指针)

💬 Q2:如何实现一个自定义同步器?

步骤示例(实现不可重入锁):

class Mutex extends AbstractQueuedSynchronizer {
    protected boolean tryAcquire(int acquires) {
        if (compareAndSetState(0, 1)) { // 仅当state=0时获取
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
        return false;
    }
    
    protected boolean tryRelease(int releases) {
        if (getState() == 0) throw new IllegalMonitorStateException();
        setExclusiveOwnerThread(null);
        setState(0); // 不需要CAS,因为只有持有线程能释放
        return true;
    }
}

💬 Q3:AQS如何处理线程中断?

中断策略

  1. 抢锁阶段:仅设置中断标记(acquireQueued返回true)
  2. 条件等待:立即抛出InterruptedException
  3. 取消节点:将waitStatus设为CANCELLED并移除

最佳实践

  1. 优先使用现有同步工具(如ReentrantLock)
  2. 自定义同步器时严格遵循模板方法规范
  3. 监控队列长度(可通过getQueuedThreads()诊断竞争情况)

通过jstack可查看AQS队列中的等待线程