java并发:AQS源码深度解读
AQS是 AbstractQueuedSynchronizer 的简称,即抽象队列同步器,从字⾯意思上理解:
抽象:抽象类,只实现⼀些主要逻辑,有些⽅法由⼦类实现;
队列:使⽤先进先出(FIFO)队列存储数据;
同步:实现了同步的功能。
AQS是⼀个⽤来构建锁和同步器的框架,使⽤AQS能简单且⾼效地构造出应⽤⼴泛的同步器,⽐如ReentrantLock、ReentrantReadWriteLock、Semaphore等皆是基于AQS的。

AbstractQueuedSynchronizer 继承了抽象类 AbstractOwnableSynchronizer

解读:ReentrantLock 通过 exclusiveOwnerThread 记录持有者
Semaphore 的内部类 Sync 继承了 AbstractQueuedSynchronizer(ReentrantLock 也有类似实现)

AQS内部使⽤了⼀个用volatile标识的变量state来作为资源的标识,state 变量是实现同步状态控制的关键【见后文State章节详述】

解读:
compareAndSetState的实现依赖于Unsafe的compareAndSwapInt()⽅法。
AQS类本身实现的是⼀些排队和阻塞的机制,⽐如线程等待队列的维护(如获取资源失败⼊队/唤醒出队等);AQS是一个FIFO的双向队列,其内部通过head和tail记录队首和队尾元素,队列元素的类型为 Node。


Node

Node 中的 SHARED 用来标记该线程是获取共享资源时被阻塞挂起后放入 AQS 队列的;EXCLUSIVE 用来标记该线程是获取独占资源时被阻塞挂起后放入 AQS 队列的;⼀般情况下,⼦类只需要根据需求实现其中⼀种模式,当然也有同时实现两种模式的同步类,如 ReadWriteLock。
Node 中的 prev 记录当前节点的前驱节点;next 记录当前节点的后继节点;thread变量用来存放进入 AQS 队列的线程;
waitStatus 记录当前线程等待状态,可以为:CANCELLED (线程被取消了)、SIGNAL (线程需要被唤醒)、CONDITION (线程在条件队列里面等待)、PROPAGATE (释放共享资源时需要通知其他节点);
nextWaiter实现Condition条件上的等待线程 队列(单向队列)

ConditionObject
AQS 的内部类 ConditionObject 结合锁实现线程同步(实现了条件变量的功能);ConditionObject 可以直接访问 AQS对象内部的变量,比如 state 和 AQS 队列。
关于ConditionObject的设计:等待队列是条件变量特有的,每个Condition实例独立维护一个队列,存放调用await()的线程;而同步队列是AQS全局的,存放等待锁的线程。
名词解释
1. 同步队列(Sync Queue)
作用:
同步队列是 AQS 的核心数据结构,用于管理竞争锁的线程。当线程尝试获取锁失败时,会被封装为 Node 节点并加入同步队列尾部,进入阻塞状态(LockSupport.park())。
特点:
全局唯一:每个 AQS 实例只有一个同步队列。
基于 CLH 变体:通过双向链表实现,支持公平锁和非公平锁的调度。
2. 等待队列(Wait Queue)
作用:
等待队列是 ConditionObject 的内部数据结构,用于管理等待特定条件的线程。当线程调用 condition.await() 时,会释放锁并加入等待队列。
特点:
每个 ConditionObject 实例独立维护一个等待队列(单向链表)。
线程在等待队列中不参与锁竞争,直到被 signal() 唤醒。
协同工作的时序
—— 线程从同步队列获取锁,在条件不满足时进入等待队列,被signal后回到同步队列重新竞争锁。

Condition接口
ConditionObject 实现了Condition接口(java.util.concurrent.locks.Condition):

await() 操作:
将当前线程加入等待队列,并释放锁(否则其他线程无法获取锁修改条件)。
伪代码:
public final void await() throws InterruptedException { // 1. 创建新节点加入等待队列尾部 Node node = addConditionWaiter(); // 2. 完全释放锁(修改 state 值) int savedState = fullyRelease(node); // 3. 阻塞线程,等待 signal while (!isOnSyncQueue(node)) { LockSupport.park(this); } // 4. 被唤醒后重新竞争锁 acquireQueued(node, savedState); }
signal() 操作:
将等待队列中的线程移回 AQS 的同步队列,使其可竞争锁。
伪代码:
public final void signal() { // 1. 检查当前线程是否持有锁 if (!isHeldExclusively()) throw new IllegalMonitorStateException(); // 2. 取出等待队列首节点 Node first = firstWaiter; if (first != null) { // 3. 将节点从等待队列移至同步队列 doSignal(first); } }
关键步骤:
校验锁状态:确保当前线程持有锁(否则抛出异常)。
转移节点:
—— 将等待队列的首节点移出,将节点的 waitStatus 从 CONDITION(-2)改为 0(初始状态)。
—— 调用 enq() 方法将其加入同步队列尾部。
—— 设置前驱节点状态:将新节点的前驱节点状态设为 SIGNAL(-1),(表示需唤醒后续节点)。
唤醒线程:当持有锁的线程调用 unlock() 时,会检查头节点的 waitStatus,若为 SIGNAL,则调用 unparkSuccessor() 唤醒其后继节点(即被转移的线程,通过 LockSupport.unpark() 唤醒线程);唤醒后,该线程在 acquireQueued() 中尝试获取锁。
Note:
唤醒操作由前驱节点释放锁时触发,而非 signal() 直接唤醒
伪代码:
public void unlock() { int state = getState() - 1; if (state == 0) { // 完全释放锁 setExclusiveOwnerThread(null); Node head = getHead(); if (head != null && head.waitStatus != 0) { unparkSuccessor(head); // 唤醒后继节点 } } }
问题:为何不立即唤醒?
此设计解决了 线程安全 和 性能 问题:
避免无效竞争:若 signal() 后立即唤醒,但锁仍被当前线程持有,被唤醒线程会再次阻塞(浪费资源)。
确保条件可见性:延迟唤醒保证被唤醒线程在获取锁后,能直接观察到最新的条件状态(其他线程已修改)。
减少上下文切换:降低线程切换频率。
时序图:

ConditionObject 是条件变量,每个条件变量在内部维护了一个条件队列 (单向链表队列),这个条件队列和 AQS 队列不是一回事。
此处的队列是用来存放调用条件变量的 await 方法后被阻塞的线程,队列的头、尾元素分别为 firstWaiter 和 lastWaiter。
Note:
调用条件变量的 await()方法就相当于调用共享变量的 wait()方法,调用条件变量的 signal方法就相当于调用共享变量的 notify()方法,调用条件变量的 signalAll ( )方法就相当于调用共享变量的 notifyAll()方法。
至此,相信大家都已经知道条件变量是什么了,它能用来做什么。
条件变量示例

示例中(2)处使用Lock 对象的 newCondition ()方法创建了一个 ConditionObject 变量(参见后续ReentrantLock源码截图),该变量就是 Lock锁对应的一个条件变量。
示例中(3)处获取了独占锁,示例中(4)处则调用了条件变量的 await ()方法阻塞挂起了当前线程。
当其他线程调用条件变量的 signal方法时,被阻塞的线程才会从 await处返回。


newCondition()方法

Note:
在调用条件变量的 signal 和 await方法前必须先获取条件变量对应的锁,如果在没有获取到锁之前调用了条件变量的 await方法则会抛出 java.lang.IllegalMonitorStateException异常。
sequenceDiagram Thread A->>+Lock: lock() Thread A->>+Condition: await() Condition->>Lock: fullyRelease() // 释放锁 Thread B->>+Lock: lock() Thread B->>Condition: signal() Condition->>AQS: transferWaiter() // 线程A移入同步队列 Thread B->>Lock: unlock() Thread A->>Lock: re-acquire lock() // 重新竞争锁
说明:
调用 await/signal 前未持有锁,await() 无法释放未持有的锁 → 死锁;signal() 无法访问私有等待队列 → 抛出 IllegalMonitorStateException
与 synchronized 的对比

一个 Lock对象可以创建多个条件变量。
AQS 只提供了 ConditionObject 的实现,并没有提供 newCondition 函数,该函数用来 new 一个 ConditionObject对象;需要由 AQS 的子类来实现 newCondition 函数。
小结:
下图反映了前面描述的关系:

详解:
当多个线程同时调用 lock.lock()方法获取锁时,只有一个线程获取到了锁,其他线程会被转换为 Node 节点插入到 lock 锁对应的 AQS 阻塞队列里面,并做自旋 CAS 尝试获取锁。
如果获取到锁的线程调用了对应条件变量的 await()方法,则该线程会释放获取到的锁,并被转换为 Node 节点插入到条件变量对应的条件队列里面。
此时因调用 lock.lock() 方法被阻塞到 AQS 队列里面的一个线程会获取到被释放的锁,如果该线程也调用了条件变量的 await ()方法则该线程也会被放入条件变量的条件队列里面。
当另外一个线程调用条件变量的 signal()或者 signalAll()方法时,会把条件队列里面的一个或者全部 Node节点移动到 AQS 的阻塞队列里面,等待时机获取锁。
设计优势:
解耦锁与条件:通过分离同步队列(锁竞争)和等待队列(条件等待),支持多个条件变量。
避免虚假唤醒:线程被 signal() 唤醒后需重新获取锁,确保条件检查在持有锁时进行(while (condition) 循环)。
高效唤醒:signal() 仅转移节点,由同步队列管理锁竞争,减少不必要的线程切换。
state
在 AQS 中维持了一个状态值 state,可以通过 getState、setState、compareAndSetState 函数修改其值,代码如下:
/** * The synchronization state. */ private volatile int state; /** * Returns the current value of synchronization state. * This operation has memory semantics of a {@code volatile} read. * @return current state value */ protected final int getState() { return state; } /** * Sets the value of synchronization state. * This operation has memory semantics of a {@code volatile} write. * @param newState the new state value */ protected final void setState(int newState) { state = newState; } /** * Atomically sets synchronization state to the given updated * value if the current state value equals the expected value. * This operation has memory semantics of a {@code volatile} read * and write. * * @param expect the expected value * @param update the new value * @return {@code true} if successful. False return indicates that the actual * value was not equal to the expected value. */ protected final boolean compareAndSetState(int expect, int update) { return STATE.compareAndSet(this, expect, update); }
对于 AQS 来说,线程同步的关键是对 state 进行操作。
state 变量的核心设计
数据类型:volatile int
通过 volatile 保证多线程间的可见性,确保状态变更对所有线程立即可见。
使用 int 类型支持灵活的位运算(如读写锁的高低位拆分)。
访问控制:
通过 getState()、setState()、compareAndSetState() 方法操作,避免直接访问字段。
compareAndSetState() 基于 CAS(Compare-And-Swap) 实现原子更新,确保并发修改的安全性(底层调用 Unsafe 类)。
state 在不同同步器中的语义
state 的具体含义由子类自定义,常见应用场景:
(1)ReentrantLock(可重入锁)
语义:记录当前线程的重入次数
state = 0:锁未被占用。
state = N(N > 0):锁被占用,且当前线程重入了 N 次。
加锁逻辑:
protected boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { setState(c + acquires); // 重入时直接累加 return true; } return false; }
(2)Semaphore(信号量)
语义:表示剩余可用许可数
state = K:初始许可数量为 K。
每次 acquire() 将 state 减 1,release() 加 1。
(3)CountDownLatch(倒计时闩)
语义:未完成的计数任务数
初始化时 state = N(N 为任务数)。
countDown() 调用 compareAndSetState 递减至 0,触发阻塞线程唤醒。
(4)ReentrantReadWriteLock(读写锁)
语义:高 16 位表示读锁数量,低 16 位表示写锁重入次数
通过位运算拆分状态:
static final int SHARED_SHIFT = 16; static int sharedCount(int c) { return c >>> SHARED_SHIFT; } static int exclusiveCount(int c) { return c & (1 << SHARED_SHIFT) - 1; }
操作 state 的线程安全机制
CAS 原子性:
所有状态变更通过 compareAndSetState() 实现,避免锁竞争(如 ReentrantLock 非公平模式直接抢锁)。
重入性支持:
通过检查当前线程是否持有锁(getExclusiveOwnerThread())实现重入计数。
避免直接修改:
必须通过 AQS 提供的 protected 方法(如 setState())修改状态,否则破坏同步逻辑。
state 变量的操作方式(独占与共享)
根据 state 是否属于一个线程,操作 state 的方式分为独占方式和共享方式。
解释:
在 AQS(AbstractQueuedSynchronizer)框架中,state 变量的操作方式(独占与共享) 本质上是根据资源访问的排他性来区分的,而非字面意义上的“属于某个线程”。
在独占模式下state确实被某个线程独占,但共享模式下state不属于任何线程,而是被多个线程共同维护的共享计数器;这个本质差异决定了两种模式在API设计、线程交互和唤醒策略上的所有区别。
补充:
独占模式:减少唤醒开销(每次只唤醒一个线程)。
共享模式:降低线程阻塞率(许可充足时无需入队)。
在实现层面共享模式其实复用独占模式的基础设施:共享模式的doAcquireShared方法内部仍然调用了addWaiter(Node.SHARED)来创建共享节点,说明共享模式是构建在独占模式的队列机制之上的扩展。
子类重写的方法不同(tryAcquire vs tryAcquireShared),但底层操作state的CAS机制是完全相同的,这个统一性反映了AQS作为框架的抽象能力。
独占方式
核心特征:资源同一时刻仅允许一个线程持有。
—— 使用独占方式获取的资源是与具体线程绑定的,也就是说如果一个线程获取到了资源,则进行标记,其他线程尝试操作 state 获取资源时会发现当前该资源的持有者不是自己,于是在获取失败后被阻塞。
—— 通过 AQS 的 exclusiveOwnerThread 字段(在 AbstractOwnableSynchronizer 中定义)记录持有者,与 state 配合实现重入性。



例子:独占锁 ReentrantLock 的实现
当一个线程获取了ReentrantLock的锁后,在 AQS 内部会使用 CAS 操作把状态值 state 从0 变为 1,然后设置当前锁的持有者为当前线程,当该线程再次获取锁时发现它就是锁的持有者,则把状态值从 1 变为 2,也就是设置可重入次数,而当另外一个线程获取锁时发现自己不是该锁的持有者就会被放入 AQS 阻塞队列后挂起。
在独占方式下获取和释放资源的方法为:
/** * Acquires in exclusive mode, ignoring interrupts. Implemented * by invoking at least once {@link #tryAcquire}, * returning on success. Otherwise the thread is queued, possibly * repeatedly blocking and unblocking, invoking {@link * #tryAcquire} until success. This method can be used * to implement method {@link Lock#lock}. * * @param arg the acquire argument. This value is conveyed to * {@link #tryAcquire} but is otherwise uninterpreted and * can represent anything you like. */ public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } /** * Acquires in exclusive mode, aborting if interrupted. * Implemented by first checking interrupt status, then invoking * at least once {@link #tryAcquire}, returning on * success. Otherwise the thread is queued, possibly repeatedly * blocking and unblocking, invoking {@link #tryAcquire} * until success or the thread is interrupted. This method can be * used to implement method {@link Lock#lockInterruptibly}. * * @param arg the acquire argument. This value is conveyed to * {@link #tryAcquire} but is otherwise uninterpreted and * can represent anything you like. * @throws InterruptedException if the current thread is interrupted */ public final void acquireInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); if (!tryAcquire(arg)) doAcquireInterruptibly(arg); }
/** * Releases in exclusive mode. Implemented by unblocking one or * more threads if {@link #tryRelease} returns true. * This method can be used to implement method {@link Lock#unlock}. * * @param arg the release argument. This value is conveyed to * {@link #tryRelease} but is otherwise uninterpreted and * can represent anything you like. * @return the value returned from {@link #tryRelease} */ public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; }
操作逻辑:
获取资源时,通过 tryAcquire() 修改 state,确保仅一个线程成功(如 state 从 0 变为 1)。
释放资源时,通过 tryRelease() 将 state 回退(如 state 从 N 变为 N-1)。
共享方式
核心特征:资源允许多个线程同时访问(数量由 state 控制)。
—— 使用共享方式获取资源与具体线程不相关;当多个线程去请求资源时通过 CAS 方式竞争。
当一个线程获取到资源后,另一个线程尝试获取资源时,如果当前资源能满足它的需要,则当前线程只需要使用 CAS 方式进行获取即可。
例子:Semaphore 信号量
当一个线程通过 acquire()方法获取信号量时,会首先看当前信号量个数是否满足需要,不满足则把当前线程放入阻塞队列,如果满足则通过自旋 CAS 获取信号量。
在共享方式下获取和释放资源的方法为:
/** * Acquires in shared mode, ignoring interrupts. Implemented by * first invoking at least once {@link #tryAcquireShared}, * returning on success. Otherwise the thread is queued, possibly * repeatedly blocking and unblocking, invoking {@link * #tryAcquireShared} until success. * * @param arg the acquire argument. This value is conveyed to * {@link #tryAcquireShared} but is otherwise uninterpreted * and can represent anything you like. */ public final void acquireShared(int arg) { if (tryAcquireShared(arg) < 0) doAcquireShared(arg); } /** * Acquires in shared mode, aborting if interrupted. Implemented * by first checking interrupt status, then invoking at least once * {@link #tryAcquireShared}, returning on success. Otherwise the * thread is queued, possibly repeatedly blocking and unblocking, * invoking {@link #tryAcquireShared} until success or the thread * is interrupted. * @param arg the acquire argument. * This value is conveyed to {@link #tryAcquireShared} but is * otherwise uninterpreted and can represent anything * you like. * @throws InterruptedException if the current thread is interrupted */ public final void acquireSharedInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); if (tryAcquireShared(arg) < 0) doAcquireSharedInterruptibly(arg); }
/** * Releases in shared mode. Implemented by unblocking one or more * threads if {@link #tryReleaseShared} returns true. * * @param arg the release argument. This value is conveyed to * {@link #tryReleaseShared} but is otherwise uninterpreted * and can represent anything you like. * @return the value returned from {@link #tryReleaseShared} */ public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { doReleaseShared(); return true; } return false; }
操作逻辑:
获取资源时,通过 tryAcquireShared() 原子性减少 state(如 state 从 K 变为 K-1)。
释放资源时,通过 tryReleaseShared() 增加 state(如 state 从 K 变为 K+1)。
小结
AQS的设计是基于模板⽅法模式的,它有⼀些⽅法必须要⼦类去实现的,AQS并没有提供可用的tryAcquire和tryRelease方法。
tryAcquire和 tryRelease需要由具体的子类来实现。
子类在实现 tryAcquire和 tryRelease时要根据具体场景使用 CAS算法尝试修改 state状态值, 成功则返回 true,否则返回 false。
子类还需要定义在调用 acquire 和 release 方法时状态值 state 的增减代表什么含义。
例子:
继承自 AQS 实现的独占锁 ReentrantLock,定义当 status 为 0 时表示锁空闲,为 1 时表示锁己经被占用。
故其在重写 tryAcquire 时,需要使用 CAS 算法查看当前 state 是否为 0,如果为 0 则使用 CAS 设置为 1,并设置当前锁的持有者为当前线程,而后返回true;如果 CAS 失败则返回 false。
同理,tryAcquireShared 和 tryReleaseShared 也需要由具体的子类来实现。
例子:
继承自 AQS 实现的读写锁 ReentrantReadWriteLock,读锁在重写 tryAcquireShared 时,首先查看写锁是否被其他线程持有,如果是则直接返回 false; 否则使用 CAS 递增 state 的高 16 位。
(在 ReentrantReadWriteLock 中, state 的高 16 位为获取读锁的次数)
基于 AQS 实现的锁除了需要重写上面介绍的方法外,还需要重写 isHeldExclusively 方法,来判断锁是被当前线程独占还是被共享。

问题:带有 Interruptibly关键字的函数和不带该关键字的函数有什么区别?
不带 Intenuptibly 关键字的方法意思是不对中断进行响应。
详解:线程在调用不带 Interruptibly 关键字的方法获取资源时或者获取资源失败被挂起时,其他线程中断了该线程,则该线程不会因为被中断而抛出异常,它还是继续获取资源或者被挂起,也就是说不对中断进行响应,忽略中断。
带 Interruptibly关键字的方法要对中断进行响应。
详解:线程在调用带 Interruptibly 关键字的方法获取资源时或者获取资源失败被挂起时,其他线程中断了该线程,则该线程抛出 InterruptedException 异常而返回。

浙公网安备 33010602011771号