并发编程

AQS 源码原理:state、同步队列与 Condition

沿着获取、入队、阻塞、唤醒和条件等待主线,理解 AbstractQueuedSynchronizer。

难度:深入更新:2026-07-16

一、AQS 解决什么问题

AQS 全称 AbstractQueuedSynchronizer,是一套构建锁和同步器的框架。它把同步器的共同机制抽出来:

  • state 表示同步状态;
  • 用 FIFO 等待队列管理竞争失败的线程;
  • 使用 CAS 修改状态和队列;
  • 使用 LockSupport.park/unpark 阻塞和唤醒;
  • 支持独占模式、共享模式和 Condition。

子类主要定义“什么条件下获取或释放状态”,AQS 负责失败后的排队、阻塞和传播。

AQS 核心数据结构:state、同步队列与等待节点

二、AQS 的核心字段

概念上最重要的是:

private volatile int state;
private transient volatile Node head;
private transient volatile Node tail;

不同 JDK 版本的 Node 字段和状态常量可能调整,但 state + FIFO 同步队列 这条主线稳定。

state 的语义由子类解释

同步器state 的含义
ReentrantLock0 表示空闲,正数表示独占锁重入次数
Semaphore剩余许可证数
CountDownLatch尚未归零的计数
ReentrantReadWriteLock高低位组合记录读写锁状态

AQS 不知道 state 具体代表什么,它只提供原子读写和排队框架。

三、模板方法设计

独占模式常由子类实现:

tryAcquire
tryRelease
isHeldExclusively

共享模式常由子类实现:

tryAcquireShared
tryReleaseShared

AQS 的公开/最终方法负责完整流程:

先尝试获取
失败则入队
在合适时机重试
仍失败则 park
释放时唤醒后继

这就是模板方法模式:框架固定流程,子类填入同步策略。

四、独占获取流程

ReentrantLock.lock() 为例,概念流程是:

tryAcquire
  ├─ 成功:设置持有线程并返回
  └─ 失败:创建等待节点
              ↓
           追加到队尾
              ↓
     前驱是 head 时再次尝试
              ↓
        仍失败则 park
              ↓
        被唤醒后循环重试

为什么入队后还要循环重试

线程可能因为以下原因醒来:

  • 前驱释放锁并 unpark
  • 中断;
  • 虚假唤醒;
  • 超时版本到期。

因此唤醒不等于已经获得锁,必须回到循环重新检查条件。

为什么通常由 head 的直接后继竞争

让队首附近节点优先尝试,可以维持大体 FIFO,减少所有等待线程同时抢锁造成的惊群。

五、AQS 等待队列是什么

AQS 的同步队列常被描述为 CLH 队列的变体。它不是最原始的“所有线程在前驱状态上纯自旋”的 CLH 锁,而是维护显式前后链接,并在需要时阻塞线程。

典型节点包含概念信息:

  • 当前等待线程;
  • 前驱、后继链接;
  • 等待状态;
  • 独占或共享模式;
  • Condition 队列链接。

head 通常是已经成功获取同步状态的节点或哨兵位置。新节点通过 CAS 竞争 tail 加入队尾。

为什么入队使用 CAS

多个线程可能同时入队,CAS 可以让一个线程成功更新 tail,失败线程重新读取队尾再尝试,避免用一把全局锁保护队列追加。

六、阻塞与唤醒

AQS 使用 LockSupport

LockSupport.park(this);
LockSupport.unpark(thread);

permit 模型

每个线程最多拥有一个 permit:

  • unpark 可以先于 park
  • permit 不会无限累积;
  • 有 permit 时下一次 park 可以直接返回;
  • park 允许虚假返回,所以必须循环检查条件。

相比 wait/notify,LockSupport 不要求调用方先持有某个对象 Monitor,并可精确指定唤醒线程。

七、释放独占锁

ReentrantLock 释放时大致是:

  1. tryRelease 减少 state;
  2. state 归零时清除独占持有线程;
  3. 检查同步队列;
  4. 唤醒合适的后继节点;
  5. 被唤醒线程重新执行获取循环。

可重入锁每次成功重入都会增加 state,因此必须对应释放相同次数,state 归零才真正释放所有权。

八、公平锁与非公平锁

非公平获取

新线程可以先直接 CAS 抢 state。即使队列中已有等待线程,它仍可能插队成功。

优点是减少线程切换、提高吞吐;缺点是等待时间方差更大,可能发生饥饿。

公平获取

获取前通常检查:

hasQueuedPredecessors()

如果前面已有等待节点,就不插队,进入或留在队列中。

公平锁只是倾向按等待顺序授予锁,不是严格实时调度保证。

九、共享模式

共享模式允许多个线程同时成功获取状态。tryAcquireShared 的返回值通常表达:

负数:获取失败
零:获取成功,但没有剩余共享能力
正数:获取成功,后继节点还可能继续获取

Semaphore

state 表示许可证数量。获取许可证减少 state,释放增加 state。只要还有许可证,多个线程就能同时通过。

CountDownLatch

state 表示剩余计数。await() 在 state 不为 0 时进入共享等待;countDown() 递减,归零后传播唤醒所有等待线程。

CountDownLatch 一次性使用,归零后不能重置。

ReentrantReadWriteLock.ReadLock

读锁使用共享模式,多个读线程可以同时成功;写锁存在时读获取失败。

十、Condition 的双队列模型

Condition 不是直接在同步队列中等待。调用 await() 的线程先进入 Condition 队列,并完全释放锁;收到 signal() 后,节点被转移到 AQS 同步队列,再重新竞争锁。

Condition await 与 signal 的队列转移流程

await 流程

线程必须持有独占锁
  ↓
加入 Condition 队列
  ↓
完全释放当前重入锁
  ↓
park 等待
  ↓
被 signal 后进入同步队列
  ↓
重新获取原来的锁
  ↓
恢复 await 前的重入次数
  ↓
await 返回

signal 流程

调用 signal 的线程也必须持有锁。signal 把 Condition 队列中等待最久的有效节点转移到同步队列,并不会把锁直接交给它。

当前 signal 线程退出临界区并释放锁后,被转移节点才有机会获取锁。

十一、中断、取消与超时

等待线程可能因为中断、超时或异常取消。队列必须跳过已取消节点,并保证后继仍有机会被唤醒。

不同 API 的中断语义不同:

  • lock() 获取过程中通常不因中断而抛出,但会保留/恢复中断状态;
  • lockInterruptibly() 等待时响应中断并退出;
  • tryLock(timeout) 同时处理超时与中断;
  • Condition.await() 可响应中断;
  • awaitUninterruptibly() 不因中断提前返回。

工程中必须清楚调用的是哪一种语义,不能捕获 InterruptedException 后直接忽略。

十二、AQS 与 ReentrantLock 的关系

ReentrantLock 外层负责 Lock API,内部 Sync 继承 AQS:

ReentrantLock
  ↓ 委托
Sync extends AQS
  ├─ NonfairSync
  └─ FairSync

state 与 owner

  • state 为 0:锁空闲;
  • 首次获取:CAS 把 state 从 0 改为 1,并设置独占线程;
  • 同线程重入:state 增加;
  • unlock:state 减少;
  • state 归零:清除 owner,唤醒后继。

只比较 state 不足以实现可重入,还必须判断当前线程是否就是独占持有者。

十三、如何阅读 AQS 源码

不要从所有字段开始背。建议按一条真实调用链:

  1. ReentrantLock.lock()
  2. 公平或非公平 tryAcquire
  3. AQS 获取失败后的入队;
  4. park 与 unpark;
  5. unlock()tryRelease
  6. 再看共享模式;
  7. 最后看 Condition 的双队列转移。

阅读时区分三个层次:

同步策略:子类如何解释 state
排队机制:AQS 如何管理失败线程
线程调度:LockSupport 如何 park/unpark

十四、常见误区

  1. AQS 不是锁,而是构建锁和同步器的框架;
  2. 入队成功不代表获得锁;
  3. unpark 不代表线程一定立即运行或获得锁;
  4. signal 不会让 Condition 线程直接执行;
  5. 公平锁不是操作系统级严格公平调度;
  6. Semaphore 控制并发数量,不等同于严格 QPS 限流;
  7. CountDownLatch 使用共享模式,但它不是可重复屏障;
  8. AQS 队列是 CLH 思想的工程变体,不是纯自旋 CLH 的原样实现。

十五、高频面试问题

  1. AQS 的 state 和同步队列分别做什么?
  2. 子类为什么只需实现 tryAcquire/tryRelease?
  3. 获取锁失败后如何入队和阻塞?
  4. 为什么线程被唤醒后还要重新竞争?
  5. 公平锁与非公平锁在获取路径上有什么区别?
  6. AQS 独占模式和共享模式有什么区别?
  7. Condition 为什么需要单独的等待队列?
  8. signal 后节点如何从 Condition 队列进入同步队列?
  9. ReentrantLock 如何通过 state 实现重入?
  10. CountDownLatch 和 Semaphore 如何解释 state?

参考资料

DISCUSSION

评论与讨论

留下你的想法