AQS的原理
AQS(AbstractQueuedSynchronizer)是 Java 并发包中绝大多数同步器(如 ReentrantLock、Semaphore、CountDownLatch 等)的基础框架。它的核心原理可以概括为:用 volatile 变量表示同步状态,配合一个 FIFO 的线程等待队列,通过 CAS 和 park/unpark 实现线程的阻塞与唤醒。
下面从几个关键部分说明其内部原理。
1. 同步状态:State
-
AQS 内部维护一个
volatile int state,表示共享资源的状态。 -
子类通过重写以下方法来定义状态的获取与释放语义:
-
tryAcquire(int arg)/tryRelease(int arg)—— 独占模式 -
tryAcquireShared(int arg)/tryReleaseShared(int arg)—— 共享模式 -
isHeldExclusively()—— 是否被当前线程独占
-
-
操作 state 时使用 CAS(
compareAndSetState)来保证原子性,避免使用重量级锁。
2. CLH 变体队列(同步队列)
AQS 内部维护一个 FIFO 的双向链表队列(基于 CLH 锁队列的变体),用于存放获取锁失败的线程。
节点结构(Node)
每个等待线程被封装为一个 Node,关键属性:
-
thread:等待的线程。 -
waitStatus:节点状态,核心值有:-
CANCELLED(1):节点因超时或中断被取消。 -
SIGNAL(-1):后继节点需要被唤醒,当前节点释放锁时必须唤醒后继。 -
CONDITION(-2):节点在条件队列中等待。 -
PROPAGATE(-3):共享模式下,唤醒动作需要向后传播。
-
-
prev/next:指向前驱和后继节点。
入队操作
-
线程获取锁失败时,用 CAS 自旋地将新节点插入队列尾部(
enq方法),保证线程安全。
出队和唤醒
-
队列的首节点(head)是当前持有锁的线程对应的节点。
-
头节点释放锁后,唤醒其后继节点(
unparkSuccessor),被唤醒的线程会尝试再次获取锁,成功后将自身设为新的头节点。
3. 独占模式原理
以 ReentrantLock 的 lock() 为例,最终调用到 AQS 的 acquire(int arg):
text
acquire(arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
-
tryAcquire:子类实现,如尝试 CAS 将 state 从 0 改为 1。 -
失败后通过
addWaiter将当前线程包装成独占节点,CAS 加入队列尾部。 -
acquireQueued中线程进入自旋(spin)逻辑:-
检查前驱是否是头节点,如果是则再次尝试
tryAcquire。 -
如果不是头节点或尝试失败,则判断是否应挂起(
shouldParkAfterFailedAcquire会将前驱的waitStatus设为SIGNAL,保证下次前驱释放时会唤醒自己),然后调用LockSupport.park(this)挂起线程。
-
-
线程被唤醒后会继续自旋,直到成功获取锁,并将自己设为头节点。
释放时调用 release(int arg):
-
tryRelease修改 state。 -
成功后若头节点存在且
waitStatus不为 0(比如为SIGNAL),则调用unparkSuccessor唤醒后继节点。
4. 共享模式原理
acquireShared(int arg) 的逻辑类似,但能支持多个线程同时访问:
-
调用
tryAcquireShared返回负值表示失败,加入共享节点。 -
在
doAcquireShared中自旋,成功后调用setHeadAndPropagate将当前节点设为头节点,并在满足条件时连续唤醒后继共享节点,实现“传播”效应(例如 Semaphore 释放一个许可,可能同时唤醒多个等待线程)。 -
释放时
releaseShared调用tryReleaseShared,成功后通过doReleaseShared唤醒后继并继续传播。
5. 条件队列(ConditionObject)
AQS 内部的 ConditionObject 实现了 Condition 接口,原理是维护另一套条件等待队列(单向链表),与同步队列配合:
-
await():-
将当前线程封装为条件节点(
waitStatus = CONDITION)加入条件队列尾部。 -
完全释放同步状态(
release),让出锁。 -
挂起当前线程,直到被
signal唤醒或中断。
-
-
signal():-
将条件队列中的首节点转移到同步队列尾部(
transferForSignal),并设置前驱的waitStatus为SIGNAL。 -
这样,当锁被释放时,该线程就能在同步队列中被唤醒并重新竞争锁。
-
6. 设计特点总结
-
模板方法模式:AQS 搭好了整个排队、挂起、唤醒的框架,子类只需实现判断能否获取/释放的几个 try 方法,极大地简化了同步器的开发。
-
无锁化的队列操作:入队、修改
waitStatus等全部基于 CAS + 自旋,避免对队列的操作加全局锁,性能极高。 -
高效的阻塞与唤醒:利用
LockSupport.park/unpark进行精准的线程挂起和唤醒,比wait/notify更轻量且无需在同步块内使用。 -
公平性支持:AQS 本身不强制公平,公平锁的实现是在
tryAcquire时调用hasQueuedPredecessors()判断是否有前驱等待节点。
简而言之,AQS 的原理就是:用 int state 表示资源,获取不到资源的线程加入 CLH 变体的 FIFO 队列,通过 CAS 保证原子变更,并通过 park/unpark 实现高效阻塞和唤醒。子类只需定义资源的获取与释放逻辑,就能快速构建出功能完备的同步组件。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)