AbstractQueuedSynchronizer
(AQS
)是 Java java.util.concurrent.locks
包中的核心同步框架,被广泛用于实现 ReentrantLock
、ReentrantReadWriteLock
、CountDownLatch
等并发工具。本文将结合 JDK 8 源码,从核心设计、关键组件、设计模式等角度深入解析其实现原理。
一、AQS 的定位与核心目标
AQS
是 Java 并发编程的“基础设施”,其核心目标是通过统一的框架简化自定义同步工具的实现。开发者只需继承 AQS
并实现几个抽象方法(如 tryAcquire
、tryRelease
),即可快速构建独占锁、共享锁、信号量等同步工具,而无需关心底层的队列管理、线程阻塞/唤醒等复杂逻辑。
二、核心组件:状态、队列与节点
AQS
的核心能力由以下三个组件支撑:
1. 同步状态:volatile int state
AQS
通过一个 volatile int
类型的 state
字段表示同步状态。其语义由子类定义:
- 对
ReentrantLock
,state
表示锁的重入次数(0 表示未锁定)。 - 对
CountDownLatch
,state
表示计数器的剩余值(初始为 N,减到 0 时释放所有等待线程)。 - 对
Semaphore
,state
表示可用许可的数量。
AQS
提供了 getState()
、setState()
和 compareAndSetState()
方法操作 state
,其中 compareAndSetState()
基于 Unsafe
的 CAS
(Compare-And-Swap
)实现原子性修改,保证多线程下的可见性和原子性。
2. 等待队列:CLH
变体的双向链表
AQS
的等待队列是 CLH
(Craig-Landin-Hagersten
)锁队列的变体,用于管理未获取到同步状态的线程。CLH
队列原本是为自旋锁设计的单向链表,AQS
将其改进为双向链表,以支持更灵活的线程取消和唤醒操作。
队列的核心字段:
head
:队列头节点(懒加载,初始为null
),表示当前持有同步状态的线程。tail
:队列尾节点,新线程入队时通过CAS
原子性地更新tail
。
队列中的每个节点(Node
)记录了:
volatile Thread thread
:当前等待的线程。volatile Node prev/next
:前驱和后继节点(双向链表)。volatile int waitStatus
:线程状态(如SIGNAL
、CANCELLED
等)。
3. 节点状态:Node.waitStatus
waitStatus
字段用于标记节点的状态,支持以下取值:
CANCELLED(1)
:线程因超时或中断被取消(永久状态)。SIGNAL(-1)
:后继节点需要被唤醒(当前节点释放时需通知后继)。CONDITION(-2)
:节点在条件队列中等待(与ConditionObject
相关)。PROPAGATE(-3)
:共享模式下,释放操作需传播给后续节点。0
:初始状态或无特殊含义。
通过 waitStatus
,AQS
可以高效管理线程的阻塞与唤醒,避免无意义的自旋或阻塞。
三、核心流程:获取与释放同步状态
AQS
的核心逻辑围绕“获取同步状态”和“释放同步状态”展开,支持独占模式(如 ReentrantLock
)和共享模式(如 CountDownLatch
)。
1. 独占模式:以 acquire(int arg)
为例
独占模式的获取流程如下(以 ReentrantLock
为例):
public final void acquire(int arg) {
if (!tryAcquire(arg) && // 子类实现:尝试获取同步状态(如锁)
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 入队并阻塞
selfInterrupt();
}
tryAcquire(arg)
:子类实现的核心方法,通过CAS
修改state
尝试获取同步状态(如ReentrantLock.Sync.tryAcquire
检查state
是否为 0 或当前线程重入)。addWaiter(Node.EXCLUSIVE)
:若获取失败,创建独占模式节点并加入队列尾部。acquireQueued(node, arg)
:循环检查前驱节点是否为头节点(尝试再次获取),若失败则阻塞当前线程(通过LockSupport.park
),直到被唤醒或中断。
2. 共享模式:以 acquireShared(int arg)
为例
共享模式允许多个线程同时获取同步状态(如 CountDownLatch
):
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0) // 子类实现:尝试获取共享状态(如计数器是否为 0)
doAcquireShared(arg); // 入队并阻塞
}
tryAcquireShared(arg)
:子类实现,返回值表示获取结果(负数表示失败,0 表示成功但无后续可能,正数表示成功且允许后续获取)。doAcquireShared(arg)
:与独占模式类似,但唤醒时需传播释放操作(通过setHeadAndPropagate
),确保后续共享节点也能被唤醒。
3. 释放同步状态:以 release(int arg)
为例
释放同步状态的流程如下(以 ReentrantLock
为例):
public final boolean release(int arg) {
if (tryRelease(arg)) { // 子类实现:尝试释放同步状态(如减少重入次数)
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h); // 唤醒头节点的后继线程
return true;
}
return false;
}
tryRelease(arg)
:子类实现,释放同步状态(如ReentrantLock.Sync.tryRelease
减少state
并检查是否完全释放)。unparkSuccessor(h)
:唤醒头节点的后继线程(通过LockSupport.unpark
),使其重新尝试获取同步状态。
四、设计模式分析
AQS
的实现中隐含了三种关键设计模式,体现了其高扩展性和灵活性。
1. 模板方法模式(Template Method Pattern)
AQS
最核心的设计模式是模板方法模式。AQS
定义了获取/释放同步状态的骨架流程(如 acquire
、release
),但将具体的同步逻辑(tryAcquire
、tryRelease
等)留给子类实现。
示例:
acquire(int arg)
方法定义了“尝试获取→失败则入队阻塞”的通用流程,而tryAcquire(arg)
由ReentrantLock.Sync
子类实现,根据锁的重入逻辑检查state
。
优点:复用公共逻辑(队列管理、线程阻塞),子类只需关注具体同步逻辑,降低了自定义同步工具的复杂度。
2. 观察者模式(Observer Pattern)
等待队列中的节点通过前驱节点的状态变化被唤醒,类似于观察者模式中“被观察对象通知观察者”的机制。
- 被观察对象:前驱节点(释放同步状态时)。
- 观察者:后继节点(需要被唤醒以重新尝试获取)。
示例:
当前驱节点释放锁(release
)时,通过 unparkSuccessor
唤醒后继节点(观察者),使其从阻塞中恢复。
优点:解耦线程的阻塞与唤醒逻辑,通过队列的链式结构高效传递唤醒信号。
3. 策略模式(Strategy Pattern)
AQS
支持通过子类实现不同的同步策略(如公平锁 vs 非公平锁),符合策略模式“算法族可替换”的思想。
示例:
ReentrantLock
的 FairSync
(公平锁)和 NonfairSync
(非公平锁)均继承自 Sync
(AQS
的子类),分别实现 tryAcquire
方法:
- 公平锁检查队列中是否有前驱线程(
hasQueuedPredecessors
),确保先到先得。 - 非公平锁直接尝试
CAS
获取锁(允许“插队”),提高吞吐量。
优点:允许在运行时动态选择同步策略(通过构造函数参数),增强了灵活性。
五、AQS
的扩展:ConditionObject
AQS
内置了 ConditionObject
类,用于实现条件变量(类似 Object.wait()
/notify()
)。条件变量支持更精细的线程等待/唤醒控制,例如:
- 线程在条件不满足时调用
condition.await()
进入条件队列。 - 当条件满足时,其他线程调用
condition.signal()
将其转移到同步队列,重新竞争锁。
ConditionObject
的核心逻辑:
- 条件队列:通过
Node.nextWaiter
链接的单向链表,存储等待条件的线程。 await()
:释放当前锁,将线程加入条件队列并阻塞。signal()
:将条件队列的头节点转移到同步队列,唤醒线程重新竞争锁。
六、AQS 的设计哲学与意义
AQS
的设计哲学可总结为:通过封装底层队列管理和线程控制,将复杂的同步逻辑简化为几个抽象方法的实现。其意义在于:
- 降低开发门槛:开发者无需手动实现队列、CAS、线程阻塞等底层操作,专注于业务逻辑。
- 保证一致性:所有基于 AQS 的同步工具(如锁、信号量)具有统一的行为(如可中断、可超时)。
- 高性能:通过 CLH 队列的变体和 CAS 操作,减少线程上下文切换,提升并发效率。
总结
AbstractQueuedSynchronizer
是 Java 并发编程的“基石”,其通过状态管理、队列控制和模板方法模式,为各种同步工具提供了统一的实现框架。理解 AQS
的源码,不仅能深入掌握 ReentrantLock
等工具的底层逻辑,更能学会如何设计高效、可扩展的多线程同步机制。掌握 AQS
,是从“使用并发工具”到“设计并发工具”的关键一步。