AQS

AbstractQueuedSynchronizer(简称 AQS)是队列同步器,顾名思义,其主要作用是处理同步。它是并发锁和很多同步工具类的实现基石(如 ReentrantLockReentrantReadWriteLockCountDownLatchSemaphoreFutureTask 等)。

AQS 的要点

AQS 提供了对独享锁与共享锁的支持

java.util.concurrent.locks 包中的相关锁(常用的有 ReentrantLockReadWriteLock)都是基于 AQS 来实现。这些锁都没有直接继承 AQS,而是定义了一个 Sync 类去继承 AQS。为什么要这样呢?因为锁面向的是使用用户,而同步器面向的则是线程控制,那么在锁的实现中聚合同步器而不是直接继承 AQS 就可以很好的隔离二者所关注的事情。

AQS 的应用

AQS 提供了对独享锁与共享锁的支持

独享锁 API

获取、释放独享锁的主要 API 如下:

1
2
3
4
public final void acquire(int arg)
public final void acquireInterruptibly(int arg)
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
public final boolean release(int arg)
  • acquire - 获取独占锁。
  • acquireInterruptibly - 获取可中断的独占锁。
  • tryAcquireNanos - 尝试在指定时间内获取可中断的独占锁。在以下三种情况下回返回:
    • 在超时时间内,当前线程成功获取了锁;
    • 当前线程在超时时间内被中断;
    • 超时时间结束,仍未获得锁返回 false。
  • release - 释放独占锁。

共享锁 API

获取、释放共享锁的主要 API 如下:

1
2
3
4
public final void acquireShared(int arg)
public final void acquireSharedInterruptibly(int arg)
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
public final boolean releaseShared(int arg)
  • acquireShared - 获取共享锁。
  • acquireSharedInterruptibly - 获取可中断的共享锁。
  • tryAcquireSharedNanos - 尝试在指定时间内获取可中断的共享锁。
  • releaseShared - 释放共享锁。

AQS 的原理

核心组成

AQS 可以理解为一个“同步状态 + 等待队列 + 模板方法”的框架:

  • 同步状态 state:AQS 使用一个 volatile int 保存同步状态,但状态的具体含义由子类定义。
    • ReentrantLock 用它表示锁的持有次数:0 表示未加锁,大于 0 表示重入次数。
    • Semaphore 用它表示剩余许可数量。
    • CountDownLatch 用它表示还需要等待的任务数量。
  • 等待队列:获取资源失败的线程会进入 AQS 维护的 FIFO 双向队列,并在合适的时机被挂起和唤醒。
  • 模板方法:AQS 负责排队、阻塞、唤醒和处理中断,子类只需要实现资源是否能够获取和释放的规则。

state 的更新通常通过 CAS 保证原子性;volatile 保证线程能够看到状态的最新值,但 volatile 本身不能保证 state++ 这类复合操作的原子性。

独占模式与共享模式

  • 独占模式:同一时刻只允许一个线程获取资源,例如 ReentrantLock。常用入口是 acquirerelease,子类实现 tryAcquiretryRelease
  • 共享模式:多个线程可以同时获取资源,例如 SemaphoreCountDownLatchReentrantReadWriteLock 的读锁。常用入口是 acquireSharedreleaseShared,子类实现 tryAcquireSharedtryReleaseShared

共享模式中的 tryAcquireShared 通常通过返回值表达结果:

  • 返回负数:获取失败;
  • 返回零:获取成功,但后续线程不一定还能获取;
  • 返回正数:获取成功,并且后续线程也可能继续获取。

获取资源的基本流程

以独占模式为例,acquire 的核心流程可以概括为:

  1. 调用子类的 tryAcquire 尝试获取资源。
  2. 获取成功,当前线程继续执行。
  3. 获取失败,将当前线程封装成节点加入等待队列。
  4. 只有排在队头且再次尝试获取成功时,线程才会被唤醒并继续执行。
  5. 线程被中断、超时或资源释放时,AQS 会按照对应模式处理节点状态。

线程等待时通常会通过 LockSupport.park 挂起,资源释放后再通过 LockSupport.unpark 唤醒后继节点。这样可以避免线程持续自旋消耗 CPU。

公平性

AQS 的等待队列为实现公平策略提供了基础,但 AQS 本身不保证公平。是否公平取决于子类的 tryAcquire 实现:

  • 公平实现通常会先检查队列中是否已有前驱节点,避免新线程插队。
  • 非公平实现可能直接通过 CAS 抢占资源,即使队列中已有等待线程。

ReentrantLock 就通过 FairSyncNonfairSync 分别实现公平锁和非公平锁。

Condition 条件队列

Condition 用于管理暂时不满足条件的线程。调用 await 的线程会从同步队列转移到条件队列,并释放当前持有的独占资源;其他线程调用 signalsignalAll 后,线程才有机会重新进入同步队列竞争锁。

因此,Condition 需要绑定到独占锁上使用,典型组合是 ReentrantLock + Condition。它解决的是“条件不满足时如何等待和通知”,而 AQS 同步队列解决的是“资源竞争失败时如何排队”。

子类需要实现什么

AQS 不关心具体资源是什么,也不直接决定资源能否获取。自定义同步器通常需要根据业务实现以下方法:

1
2
3
4
protected boolean tryAcquire(int arg)
protected boolean tryRelease(int arg)
protected int tryAcquireShared(int arg)
protected boolean tryReleaseShared(int arg)

然后直接复用 AQS 提供的 acquirereleaseacquireSharedreleaseShared 完成排队、阻塞与唤醒。

日常开发优先使用 ReentrantLockSemaphoreCountDownLatch 等现成组件,只有在需要自定义资源控制规则时才直接继承 AQS。

参考