请解释AQS在 Java 并发编程中的实现原理。

参考回答

AQS(AbstractQueuedSynchronizer) 是 Java 并发包中的一个重要基础类,位于 java.util.concurrent.locks 包下。它是用来构建同步工具(如锁、信号量、CountDownLatch 等)的框架,为实现线程的排队、状态管理以及线程同步提供了统一的低级支持。

AQS 的核心原理:

  1. 状态(state):使用一个整型变量 state 表示同步状态(如锁是否被占用、信号量的剩余许可等)。
  2. 队列(CLH 队列):使用一个双向队列存储等待获取同步状态的线程,线程以节点(Node)形式入队。
  3. 模板方法:通过 acquirerelease 方法管理线程的获取和释放,具体逻辑由子类实现。

详细讲解与拓展

1. AQS 的核心组成部分

  1. 同步状态(state)
    • 是一个 volatile 的整型变量,表示共享资源的状态。
    • 子类通过重写 tryAcquiretryRelease 等方法,自定义对状态的操作。
    • 例如:
      • 在独占锁中,state 为 1 表示锁已被占用,0 表示未被占用。
      • 在共享锁中,state 表示剩余许可的数量。
  2. 等待队列(CLH 队列)
    • AQS 使用一个基于 CLH 队列 的双向链表来保存等待锁的线程。
    • 每个线程被封装成一个 Node 对象,并通过 prevnext 指针形成双向链表。
    • 线程通过 CAS 操作安全地入队和出队。
  3. Node 类
    • 每个节点表示一个线程,其状态包括:
      • CANCELLED:线程已取消。
      • SIGNAL:线程需要被唤醒。
      • CONDITION:线程正在等待条件。
      • PROPAGATE:线程的共享模式需要传播。
  4. 模板方法
    • AQS 提供了模板方法(如 acquirerelease),具体的同步逻辑由子类实现。
    • 子类需要实现:
      • 独占模式:tryAcquiretryRelease
      • 共享模式:tryAcquireSharedtryReleaseShared

2. AQS 的工作流程

AQS 的核心是对线程的同步状态管理,包括获取和释放同步状态。以下以独占模式为例,展示 AQS 的工作流程。

2.1 获取锁(acquire 方法)

public final void acquire(int arg) {
    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
  1. 调用子类实现的 tryAcquire 尝试获取锁:
    • 如果成功,线程直接进入临界区。
    • 如果失败,将线程封装成一个 Node 并加入等待队列。
  2. 在等待队列中,线程通过 acquireQueued 方法自旋等待:
    • 当前节点检查前驱节点是否释放锁(SIGNAL 状态)。
    • 如果未释放,线程进入阻塞状态,等待被唤醒。
  3. 线程被唤醒后,再次尝试获取锁。

2.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 方法):
    • 将等待线程从阻塞状态变为可运行状态。

3. AQS 的实现模式

AQS 支持两种同步模式:

  1. 独占模式(Exclusive):
  • 每次只有一个线程能够获取同步状态。
  • 典型实现:ReentrantLock
  1. 共享模式(Shared):
  • 多个线程可以共享同步状态。
    • 典型实现:SemaphoreCountDownLatch

示例:ReentrantLock 的独占模式

protected final boolean tryAcquire(int arg) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) { // 未被占用
        if (compareAndSetState(0, arg)) { // CAS 修改 state
            setExclusiveOwnerThread(current); // 设置当前线程为持有者
            return true;
        }
    } else if (current == getExclusiveOwnerThread()) { // 重入
        setState(c + arg);
        return true;
    }
    return false;
}

示例:Semaphore 的共享模式

protected final int tryAcquireShared(int arg) {
    for (;;) {
        int available = getState();
        int remaining = available - arg;
        if (remaining < 0 || compareAndSetState(available, remaining))
            return remaining;
    }
}

4. AQS 的扩展

  1. 常见实现类
    • ReentrantLock:独占模式,基于 AQS 管理锁的获取和释放。
    • Semaphore:共享模式,用于限制资源访问的线程数。
    • CountDownLatch:共享模式,通过计数器控制线程的执行顺序。
    • CyclicBarrier:基于 Condition 实现线程的同步屏障。
  2. 自定义同步器
    • 通过继承 AQS 并重写 tryAcquiretryRelease 等方法,可以实现自定义同步工具。

示例:简单独占锁

class SimpleLock extends AbstractQueuedSynchronizer {
    @Override
    protected boolean tryAcquire(int arg) {
        return compareAndSetState(0, 1); // 0 表示未占用,1 表示占用
    }

    @Override
    protected boolean tryRelease(int arg) {
        setState(0); // 释放锁
        return true;
    }

    public void lock() {
        acquire(1); // 获取锁
    }

    public void unlock() {
        release(1); // 释放锁
    }
}

5. AQS 的优缺点

优点

  1. 高度可扩展
    • 通过重写模板方法,可以方便地实现各种同步工具。
  2. 高性能
    • 基于 CAS 操作和 CLH 队列实现,无需传统的线程上下文切换。
  3. 线程安全
    • 使用 volatile 和原子操作保证线程安全。

缺点

  1. 实现复杂:
    • AQS 的源码较复杂,新手不易理解。
  2. 锁的竞争激烈时性能下降:
    • 阻塞线程需要挂起和唤醒,开销较大。

总结

  • AQS 是并发包的核心基础: 它通过管理同步状态和队列,为锁、信号量等工具提供了强大的支持。
  • 核心原理: 使用 state 表示同步状态,使用 CLH 队列管理线程的等待顺序。
  • 适用场景: 需要自定义复杂同步逻辑的场景,例如实现锁、信号量、栅栏等工具。

发表评论

后才能评论