Java的AQS框架设计原理

Java 的 AQS(AbstractQueuedSynchronizer) 是并发包(java.util.concurrent.locks)的核心框架,为构建锁和同步器提供了底层支持。以下是其核心原理和实现机制的深度解析:


一、AQS 核心设计思想

  1. 模板方法模式
    AQS 是一个抽象类,定义了同步器的骨架(如获取/释放资源的流程),具体实现由子类完成(如 ReentrantLockSemaphore)。

  2. CLH 变种队列
    通过 双向链表 实现的等待队列(FIFO),存储被阻塞的线程。

  3. 状态管理
    使用 volatile int state 表示共享资源状态(如锁的重入次数、信号量的许可数)。


二、核心数据结构

1
2
3
4
5
6
7
8
9
10
11
12
13
// AQS 内部类 Node 表示等待线程
static final class Node {
volatile int waitStatus; // 等待状态(CANCELLED、SIGNAL等)
volatile Node prev; // 前驱节点
volatile Node next; // 后继节点
volatile Thread thread; // 关联的线程
Node nextWaiter; // 条件队列专用
}

// AQS 关键字段
private transient volatile Node head; // 队列头节点
private transient volatile Node tail; // 队列尾节点
private volatile int state; // 同步状态

三、核心方法及流程

1. 独占模式(如 ReentrantLock)

  • 获取资源 (acquire)

    1
    2
    3
    4
    5
    public final void acquire(int arg) {
    if (!tryAcquire(arg) && // 子类实现尝试获取资源
    acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 加入队列并阻塞
    selfInterrupt();
    }
    • tryAcquire(arg):子类实现(如 ReentrantLock 判断当前线程是否可获取锁)。
    • addWaiter():将线程包装为 Node 加入队列尾部。
    • acquireQueued():自旋尝试获取资源,失败后调用 LockSupport.park() 阻塞。
  • 释放资源 (release)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public final boolean release(int arg) {
    if (tryRelease(arg)) { // 子类实现释放资源
    Node h = head;
    if (h != null && h.waitStatus != 0)
    unparkSuccessor(h); // 唤醒后继节点
    return true;
    }
    return false;
    }

2. 共享模式(如 Semaphore)

  • 获取资源 (acquireShared)
    1
    2
    3
    4
    public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0) // 子类实现尝试获取共享资源
    doAcquireShared(arg); // 加入队列并阻塞
    }
  • 释放资源 (releaseShared)
    1
    2
    3
    4
    5
    6
    7
    public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) { // 子类实现释放共享资源
    doReleaseShared(); // 唤醒后续共享节点
    return true;
    }
    return false;
    }

四、关键机制解析

1. 线程阻塞与唤醒

  • 阻塞:通过 LockSupport.park() 挂起线程。
  • 唤醒:前驱节点释放资源时,调用 unparkSuccessor() 唤醒后继节点。

2. 公平性与非公平性

  • 公平锁:严格按队列顺序获取资源(检查队列是否有等待线程)。
  • 非公平锁:新线程直接尝试抢锁(ReentrantLock 默认非公平)。

3. 可重入实现

  • state 记录重入次数:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // ReentrantLock.Sync 的实现
    protected final boolean tryAcquire(int acquires) {
    Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
    if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
    setExclusiveOwnerThread(current);
    return true;
    }
    }
    else if (current == getExclusiveOwnerThread()) { // 重入逻辑
    int nextc = c + acquires;
    setState(nextc);
    return true;
    }
    return false;
    }

4. 条件变量(Condition)

  • ConditionObject:AQS 的内部类,实现条件等待队列。
  • await():释放锁并加入条件队列,阻塞当前线程。
  • signal():将条件队列中的线程转移到同步队列,等待获取锁。

五、AQS 在 JUC 中的应用

同步器 AQS 使用方式
ReentrantLock 独占模式,state 记录重入次数
Semaphore 共享模式,state 记录剩余许可数
CountDownLatch 共享模式,state 初始为计数
ReentrantReadWriteLock 高16位读锁,低16位写锁

六、源码级执行流程(以 ReentrantLock 为例)

  1. 线程 T1 获取锁
    • lock()tryAcquire(1) 成功(state=1),直接获得锁。
  2. 线程 T2 竞争锁
    • tryAcquire(1) 失败 → addWaiter(Node.EXCLUSIVE) 加入队列尾部。
    • acquireQueued() 自旋尝试获取锁,失败后阻塞。
  3. T1 释放锁
    • unlock()tryRelease(1) 设置 state=0
    • unparkSuccessor() 唤醒 T2。

七、AQS 的优劣分析

优势 局限性
灵活支持多种同步器(锁、信号量等) 需要深入理解才能正确实现子类
高性能(CAS + CLH 队列优化) 不直接支持超时中断以外的复杂条件
避免优先级反转(FIFO 队列) 调试复杂(需分析队列和状态变化)

八、关键面试问题

  1. 为什么 AQS 使用双向链表?

    • 便于处理取消节点(CANCELLED 状态)时快速断开链接。
  2. state 为什么用 volatile?

    • 保证多线程间的可见性,CAS 操作依赖于此。
  3. 如何实现非公平锁?

    • 新线程直接尝试 CAS 修改 state,不检查队列是否有等待线程。
  4. AQS 和 Synchronized 的区别?

    • AQS 更灵活(可中断、超时、公平性控制),Synchronized 由 JVM 优化(锁升级)。

通过理解 AQS 的底层机制,可以更高效地使用 JUC 工具,并能自定义符合特定需求的同步器。