AQS
AbstractQueuedSynchronizer(简称 AQS)是队列同步器,顾名思义,其主要作用是处理同步。它是并发锁和很多同步工具类的实现基石(如ReentrantLock、ReentrantReadWriteLock、CountDownLatch、Semaphore、FutureTask等)。
AQS 的要点
AQS 提供了对独享锁与共享锁的支持。
在 java.util.concurrent.locks 包中的相关锁(常用的有 ReentrantLock、 ReadWriteLock)都是基于 AQS 来实现。这些锁都没有直接继承 AQS,而是定义了一个 Sync 类去继承 AQS。为什么要这样呢?因为锁面向的是使用用户,而同步器面向的则是线程控制,那么在锁的实现中聚合同步器而不是直接继承 AQS 就可以很好的隔离二者所关注的事情。
AQS 的应用
AQS 提供了对独享锁与共享锁的支持。
独享锁 API
获取、释放独享锁的主要 API 如下:
1 | public final void acquire(int arg) |
acquire- 获取独占锁。acquireInterruptibly- 获取可中断的独占锁。tryAcquireNanos- 尝试在指定时间内获取可中断的独占锁。在以下三种情况下回返回:- 在超时时间内,当前线程成功获取了锁;
- 当前线程在超时时间内被中断;
- 超时时间结束,仍未获得锁返回 false。
release- 释放独占锁。
共享锁 API
获取、释放共享锁的主要 API 如下:
1 | public final void acquireShared(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。常用入口是acquire、release,子类实现tryAcquire和tryRelease。 - 共享模式:多个线程可以同时获取资源,例如
Semaphore、CountDownLatch和ReentrantReadWriteLock的读锁。常用入口是acquireShared、releaseShared,子类实现tryAcquireShared和tryReleaseShared。
共享模式中的 tryAcquireShared 通常通过返回值表达结果:
- 返回负数:获取失败;
- 返回零:获取成功,但后续线程不一定还能获取;
- 返回正数:获取成功,并且后续线程也可能继续获取。
获取资源的基本流程
以独占模式为例,acquire 的核心流程可以概括为:
- 调用子类的
tryAcquire尝试获取资源。 - 获取成功,当前线程继续执行。
- 获取失败,将当前线程封装成节点加入等待队列。
- 只有排在队头且再次尝试获取成功时,线程才会被唤醒并继续执行。
- 线程被中断、超时或资源释放时,AQS 会按照对应模式处理节点状态。
线程等待时通常会通过 LockSupport.park 挂起,资源释放后再通过 LockSupport.unpark 唤醒后继节点。这样可以避免线程持续自旋消耗 CPU。
公平性
AQS 的等待队列为实现公平策略提供了基础,但 AQS 本身不保证公平。是否公平取决于子类的 tryAcquire 实现:
- 公平实现通常会先检查队列中是否已有前驱节点,避免新线程插队。
- 非公平实现可能直接通过 CAS 抢占资源,即使队列中已有等待线程。
ReentrantLock 就通过 FairSync 和 NonfairSync 分别实现公平锁和非公平锁。
Condition 条件队列
Condition 用于管理暂时不满足条件的线程。调用 await 的线程会从同步队列转移到条件队列,并释放当前持有的独占资源;其他线程调用 signal 或 signalAll 后,线程才有机会重新进入同步队列竞争锁。
因此,Condition 需要绑定到独占锁上使用,典型组合是 ReentrantLock + Condition。它解决的是“条件不满足时如何等待和通知”,而 AQS 同步队列解决的是“资源竞争失败时如何排队”。
子类需要实现什么
AQS 不关心具体资源是什么,也不直接决定资源能否获取。自定义同步器通常需要根据业务实现以下方法:
1 | protected boolean tryAcquire(int arg) |
然后直接复用 AQS 提供的 acquire、release、acquireShared 和 releaseShared 完成排队、阻塞与唤醒。
日常开发优先使用 ReentrantLock、Semaphore、CountDownLatch 等现成组件,只有在需要自定义资源控制规则时才直接继承 AQS。



