在 Java 并发编程体系中,锁是解决多线程共享资源竞争、保证线程安全的核心机制。从 JVM 内置的synchronized隐式锁,到 JUC 包提供的Lock显式锁,再到支撑整个并发工具集的底层基石 AbstractQueuedSynchronizer(AQS),构成了一套层层递进、设计精妙的锁实现体系。
本文将从上层 API 到底层源码,完整拆解 Java 锁的核心原理,覆盖独占模式与共享模式、公平锁与非公平锁、条件队列与双队列联动、读写锁实现等核心知识点,结合 JDK 源码逐层剖析其设计思想与执行流程。
一、Java 锁的两类核心实现:synchronized 与 Lock
1.1 synchronized:JVM 内置隐式锁
synchronized是 JVM 层面实现的内置互斥锁,核心特性是隐式获取与释放锁 ,无需开发者手动干预:
- 进入同步代码块 / 同步方法时,JVM 自动为当前线程加锁;代码正常执行完成或抛出异常时,自动释放锁
- 底层依托对象头的 Mark Word 实现,内置偏向锁、轻量级锁、重量级锁的自适应升级机制
- 局限性突出:不支持响应中断、不支持超时获取、仅能绑定一个等待条件,无法适配复杂的并发协作场景
1.2 Lock:API 层显式锁的灵活控制
Lock是java.util.concurrent.locks包下的顶层接口,属于 API 层面的显式锁,需要手动调用方法完成锁的获取与释放,相比synchronized具备更强的可操作性与场景适配能力:
- 支持可中断获取锁:等待过程中可以响应线程中断,终止等待
- 支持超时获取锁:在指定时长内尝试获取锁,超时自动放弃,避免永久阻塞
- 支持绑定多个条件变量,可实现更精细的等待 / 通知逻辑
- 支持公平锁与非公平锁模式切换,可根据业务场景选择调度策略
Lock 接口的核心方法如下:
lock():阻塞式获取锁,获取成功前持续等待,不响应中断unlock():释放锁,必须手动调用,通常放在finally块中确保执行tryLock():非阻塞尝试获取锁,立即返回布尔结果,不阻塞线程lockInterruptibly():可中断式获取锁,等待过程中响应中断并抛出异常newCondition():创建绑定当前锁的Condition条件变量,一个锁可绑定多个独立条件
JUC 中 Lock 的核心实现类ReentrantLock,以及读写锁、信号量等工具,其底层全部基于 AQS 框架实现。
二、AQS:并发同步器的核心基石
AbstractQueuedSynchronizer(简称 AQS)是 Java 并发包中绝大多数同步工具(ReentrantLock、Semaphore、CountDownLatch、ReentrantReadWriteLock 等)的底层实现框架,它定义了一套多线程访问共享资源的通用同步机制,通过模板方法模式将通用逻辑封装,子类仅需实现少量业务方法即可定制同步器。
2.1 核心组件 1:volatile 修饰的同步状态 state
AQS 通过一个被volatile修饰的整型变量state表示同步状态,这是整个同步器的核心状态变量:
arduino
private volatile int state;
volatile保证了 state 在多线程间的可见性 ,AQS 同时提供compareAndSetState(int expect, int update)方法,基于 CAS 操作保证 state 修改的原子性,避免多线程并发修改的线程安全问题。
2.2 核心组件 2:FIFO 双向同步队列
当线程获取同步状态失败时,AQS 会将线程封装为Node节点,加入一个FIFO(先进先出)的双向链表同步队列,通过队列实现线程的等待调度与唤醒管理。
队列的节点为 AQS 的内部类Node,核心字段如下:
arduino
static final class Node {
// 节点等待状态
volatile int waitStatus;
// 前驱节点指针
volatile Node prev;
// 后继节点指针
volatile Node next;
// 节点绑定的等待线程
volatile Thread thread;
// 条件队列后继节点,区分独占/共享模式
Node nextWaiter;
}
- 队列结构:双向链表,包含头节点
head和尾节点tail,头节点代表当前持有同步状态的节点,后续节点按入队顺序等待 - 节点模式:分为独占模式
Node.EXCLUSIVE和共享模式Node.SHARED,通过nextWaiter字段区分
三、AQS 独占式同步状态的获取与释放
独占模式的核心特性是:同一时间只能有一个线程持有同步状态,对应 ReentrantLock 等互斥锁的实现。
3.1 独占式获取:acquire () 源码全解析
acquire(int arg)是独占模式下获取同步状态的入口方法,也是lock()方法的底层核心:
scss
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
执行流程分为四步:
- 尝试快速获取 :调用
tryAcquire(arg)尝试直接获取同步状态,成功则方法直接返回 - 节点封装入队 :获取失败时,调用
addWaiter将当前线程封装为独占模式节点,加入同步队列尾部 - 队列自旋等待 :调用
acquireQueued,让节点在队列中自旋等待,直到获取到同步状态 - 中断标记补全 :若等待过程中线程被中断过,最后调用
selfInterrupt()补上中断标记
分步核心源码解析
-
tryAcquire:子类实现的获取逻辑 AQS 本身不实现具体的获取逻辑,交由子类根据业务特性重写,是模板方法模式的核心体现:
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
例如 ReentrantLock 的公平锁与非公平锁,就在该方法中实现了不同的抢占规则。
-
addWaiter:节点加入同步队列 将当前线程封装为 Node 节点,先通过 CAS 快速尝试加入队尾,失败则进入自旋入队逻辑:
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// 快速尝试:CAS直接设置队尾
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 快速尝试失败,进入自旋+CAS的enq方法
enq(node);
return node;
}
3.enq:自旋保证节点安全入队 通过死循环 + CAS,保证高并发下节点一定能成功入队,同时完成队列的懒初始化:
private Node enq(final Node node) {
for (;;) {
Node t = tail;
// 队列为空,初始化哨兵头节点
if (t == null) {
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
4.acquireQueued:队列中自旋获取锁 节点入队后进入自旋逻辑:只有前驱节点是头节点时,才尝试获取同步状态;否则调整前驱状态后阻塞等待:
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
// 前驱是头节点,才有资格尝试获取锁
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // 帮助GC回收旧头节点
failed = false;
return interrupted;
}
// 获取失败,判断是否需要阻塞当前线程
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
3.2 waitStatus 节点等待状态详解
Node 节点的
waitStatus字段控制着线程的等待状态与唤醒逻辑,JDK 1.8 中共有 5 种取值:
| 状态值 | 常量名 | 含义说明 |
|---|---|---|
| 0 | - | 初始状态,节点刚创建入队时的默认状态 |
| -1 | SIGNAL | 当前节点的后继节点处于阻塞状态,当前节点释放同步状态或取消时,必须唤醒后继节点 |
| 1 | CANCELLED | 线程因超时、中断等原因取消等待,节点作废,不再参与同步竞争 |
| -2 | CONDITION | 节点位于 Condition 条件队列中,等待条件满足,此时不在同步队列内 |
| -3 | PROPAGATE | 共享模式下,唤醒操作需要向后传播,确保所有等待的共享节点都能被通知 |
其中shouldParkAfterFailedAcquire方法是状态流转的核心,负责调整前驱节点状态并判断当前线程是否可以安全阻塞:
arduino
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
// 前驱已设为SIGNAL,当前线程可以安全阻塞
if (ws == Node.SIGNAL)
return true;
// 前驱节点已取消,向前跳过所有取消节点,重新链接队列
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// 前驱为初始状态或PROPAGATE,CAS修改为SIGNAL
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
3.3 正常流程与取消流程的状态流转
正常流程下的状态流转
在无取消、无异常的正常竞争场景下,节点的状态流转如下:
- 节点入队:新创建的节点 waitStatus 为初始值 0,加入队列尾部
- 状态修改 :节点执行
shouldParkAfterFailedAcquire,将前驱节点的 waitStatus 从 0 CAS 修改为 SIGNAL (-1) - 线程阻塞 :下一轮自旋再次尝试获取失败后,确认前驱状态为 SIGNAL,调用
LockSupport.park()阻塞当前线程 - 被唤醒:头节点释放同步状态时,唤醒后继节点,当前线程从 park 中苏醒
- 获取成功:线程再次尝试获取同步状态,成功后将自己设为新的头节点,旧头节点出队,完成一次状态流转
取消流程下的状态流转
当线程等待过程中发生异常、中断或超时,会触发节点取消流程,核心逻辑在cancelAcquire方法中:
ini
private void cancelAcquire(Node node) {
if (node == null)
return;
node.thread = null;
// 向前遍历,跳过所有已取消的前驱节点
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
Node predNext = pred.next;
// 将当前节点状态设为取消
node.waitStatus = Node.CANCELLED;
// 情况1:当前节点是尾节点,更新队尾指针
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
int ws;
// 情况2:当前节点不是头节点的后继,将前驱与后继节点链接
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
// 情况3:当前节点是头节点的后继,直接唤醒后继节点
unparkSuccessor(node);
}
node.next = node; // 帮助GC
}
}
取消流程的核心是:将节点标记为 CANCELLED,调整队列双向指针,把取消节点从同步队列中剔除,保证队列的有效性。
3.4 独占式同步状态释放
release(int arg)是独占模式下释放同步状态的入口方法:
java
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)尝试释放同步状态,由子类实现具体逻辑,释放成功返回 true - 释放成功后,检查头节点状态:若头节点存在且 waitStatus 不为 0,调用
unparkSuccessor唤醒后继等待线程
唤醒后继节点的unparkSuccessor方法:
ini
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
// 从后往前找第一个有效后继节点
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
为什么从队尾往前找后继节点?因为节点入队时先设置 prev 指针、再 CAS 更新 tail、最后设置前驱的 next 指针,从后往前遍历能保证不会漏掉刚入队的节点,避免空指针问题。
四、ReentrantLock:公平锁与非公平锁源码对比
ReentrantLock 是 AQS 独占模式最经典的实现,也是可重入互斥锁的标准实现。它通过内部两个 AQS 子类,实现了公平锁(FairSync) 与非公平锁(NonfairSync) 两种模式,二者的核心差异完全体现在对tryAcquire方法的不同实现上。
4.1 ReentrantLock 整体类结构
ReentrantLock 内部维护了一个继承自 AQS 的抽象内部类Sync,作为公共逻辑基类;再由FairSync和NonfairSync两个子类分别实现公平与非公平策略。
scala
public class ReentrantLock implements Lock, java.io.Serializable {
// 同步器基类,继承AQS
abstract static class Sync extends AbstractQueuedSynchronizer {
abstract void lock();
// 公共非公平获取逻辑,供非公平锁直接调用
final boolean nonfairTryAcquire(int acquires) { ... }
}
// 非公平锁实现
static final class NonfairSync extends Sync { ... }
// 公平锁实现
static final class FairSync extends Sync { ... }
// 默认构造:非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
// 传入true指定公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
}
可重入特性:两种锁都支持重入,即同一个线程可以多次获取同一把锁,state值随重入次数递增,释放时对应递减,直到 state 归 0 才算完全释放锁。重入逻辑在两种模式下完全一致。
4.2 非公平锁 NonfairSync 源码解析
非公平锁是ReentrantLock的默认实现,核心特点是:线程获取锁时不考虑队列中是否有等待线程,直接尝试抢占,抢占失败才进入队列排队。
lock () 入口:上来就插队
非公平锁的lock()方法,在进入 AQS 的acquire流程之前,会先通过 CAS 直接尝试抢一次锁,这是第一次插队机会。
scss
final void lock() {
// 第一次插队:直接CAS尝试把state从0改成1
if (compareAndSetState(0, 1))
// 抢锁成功,设置当前线程为锁的持有者
setExclusiveOwnerThread(Thread.currentThread());
else
// 抢占失败,走AQS标准的acquire获取流程
acquire(1);
}
tryAcquire:再次插队,不判断队列
非公平锁的tryAcquire直接调用基类的nonfairTryAcquire,核心逻辑是:只要锁空闲(state=0),不管同步队列里有没有等待了很久的线程,直接 CAS 抢锁,这是第二次插队机会。
java
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
// Sync基类中的公共非公平获取逻辑
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 锁处于空闲状态
if (c == 0) {
// 直接CAS抢锁,不判断队列是否有等待线程
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 锁已被占用,判断是不是当前线程持有(重入逻辑)
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // 重入次数溢出
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
// 抢锁失败
return false;
}
4.3 公平锁 FairSync 源码解析
公平锁的核心原则是先来先服务(FIFO) :线程获取锁时,必须先检查同步队列中是否有其他线程在等待,只有队列中没有更早的等待线程时,才尝试获取锁,否则直接进入队列排队。
lock () 入口:不插队,直接走标准流程
公平锁的lock()方法没有提前抢锁的操作,直接调用 AQS 的acquire方法,严格遵守排队规则。
scss
final void lock() {
acquire(1);
}
tryAcquire:公平性核心,先判断队列
公平锁的tryAcquire与非公平锁的唯一区别,就是在锁空闲时,会先调用hasQueuedPredecessors()判断队列中是否有前驱等待线程,只有没有更早的等待者时,才尝试获取锁。
java
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 核心差异:先判断是否有更早的等待线程,没有才允许抢锁
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 重入逻辑与非公平锁完全一致
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
公平性核心:hasQueuedPredecessors
这是实现公平锁的关键方法,用于判断同步队列中是否存在比当前线程等待更久的线程。
ini
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
// 返回true:队列中有更早的等待线程,当前线程不能插队
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
逻辑拆解:
h != t:头节点不等于尾节点,说明队列不为空,有线程在等待(s = h.next) == null:头节点的后继节点为空,说明有线程正在执行入队操作(已经设置了 tail,但还没设置 prev 的 next 指针),此时认为队列中有等待者s.thread != Thread.currentThread():后继节点存在,但绑定的线程不是当前线程,说明有其他线程比当前线程等待更久
只要满足 "队列不为空,且第一个等待线程不是当前线程",就返回 true,当前线程必须排队,从而保证公平性。
4.4 公平锁 vs 非公平锁 核心对比
| 对比维度 | 非公平锁(默认) | 公平锁 |
|---|---|---|
| 插队机会 | 两次插队:lock 时直接 CAS 抢锁;tryAcquire 时再次抢锁 | 无任何插队机会,严格遵循 FIFO |
| 核心判断 | 锁空闲就直接抢,不关心等待队列 | 锁空闲时必须先判断队列,无更早等待者才抢 |
| 吞吐量 | 高,线程挂起唤醒的上下文切换次数少 | 低,所有线程严格排队,上下文切换频繁 |
| 线程饥饿 | 可能出现:新来的线程一直插队,队列中的线程长期获取不到锁 | 不会出现,所有线程按等待顺序依次获取 |
| 适用场景 | 绝大多数业务场景,追求高吞吐量、高性能 | 对执行顺序有严格要求,需要避免饥饿的场景 |
五、Condition 条件队列:双队列联动与等待唤醒机制
Condition 是显式锁体系对 "等待 - 通知" 模式的进阶实现。它解决了内置锁 Object.wait()/notify() 只能绑定一个等待队列、无法实现精准唤醒的缺陷,一个 Lock 可以绑定多个独立的 Condition 条件队列,分别对应不同的等待条件,典型应用如 ArrayBlockingQueue 的 "队列非空""队列非满" 双条件设计。
Condition 本身是接口,其核心实现是 AQS 的内部类 ConditionObject,它依托 AQS 的同步队列,额外维护了一套独立的条件等待队列,节点在 "条件队列" 与 "同步队列" 之间完成状态流转,实现等待与唤醒的完整闭环。
5.1 条件队列的结构设计
核心字段与链表结构
ConditionObject 内部维护了一个**单向链表**结构的条件等待队列,通过头尾两个指针定位队列,节点复用 AQS 的 Node 内部类,通过 nextWaiter 字段串联链表。
java
public class ConditionObject implements Condition, java.io.Serializable {
// 条件队列头节点:第一个等待条件的节点
private transient Node firstWaiter;
// 条件队列尾节点:最后一个等待条件的节点
private transient Node lastWaiter;
// 中断处理模式:等待结束后抛出中断异常
private static final int THROW_IE = -1;
// 中断处理模式:等待结束后重置中断标记
private static final int REINTERRUPT = 1;
}
同步队列与条件队列的核心差异
条件队列中的节点,waitStatus 固定为 Node.CONDITION(-2),代表节点正在等待条件触发,暂时脱离锁的竞争。
| 维度 | 同步队列(AQS 内置) | 条件队列(Condition 维护) |
|---|---|---|
| 链表结构 | 双向链表,prev/next 指针 | 单向链表,nextWaiter 指针 |
| 等待目标 | 等待获取锁(同步状态) | 等待某个业务条件满足 |
| 节点状态 | 0、SIGNAL(-1)、CANCELLED(1)、PROPAGATE(-3) | CONDITION(-2) |
| 数量 | 1 个 Lock 对应 1 个同步队列 | 1 个 Lock 可对应 N 个条件队列 |
| 节点归属 | 未抢到锁的线程全部在此排队 | 主动调用 await () 的线程在此等待 |
一个节点同一时间只能存在于一个队列中:要么在同步队列抢锁,要么在条件队列等条件,唤醒过程本质就是节点从条件队列迁移到同步队列的过程。
5.2 await () 等待流程源码解析
调用 await() 的前置条件:当前线程必须已经持有对应 Lock 锁 ,这和 Object.wait() 必须在同步块中调用是同一逻辑 ------ 保证队列修改与锁释放的线程安全性。
await() 的完整执行逻辑可以概括为五步:
- 线程安全地创建节点,加入条件队列尾部
- 完全释放当前持有的锁(处理可重入场景)
- 阻塞当前线程,等待被唤醒或中断
- 被唤醒后,从条件队列迁移至同步队列
- 在同步队列中重新竞争锁,竞争成功后恢复执行
await () 入口主流程
scss
public final void await() throws InterruptedException {
// 1. 前置校验:线程已中断则直接抛异常
if (Thread.interrupted())
throw new InterruptedException();
// 2. 创建CONDITION状态节点,加入条件队列尾部
Node node = addConditionWaiter();
// 3. 完全释放锁(含重入次数),返回释放前的state值
int savedState = fullyRelease(node);
int interruptMode = 0;
// 4. 自旋:只要节点还没进入同步队列,就持续阻塞
while (!isOnSyncQueue(node)) {
// 阻塞当前线程
LockSupport.park(this);
// 检查是否因中断被唤醒,记录中断模式
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
// 5. 节点已进入同步队列,调用acquireQueued重新抢锁
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
// 6. 清理条件队列中已取消的节点
if (node.nextWaiter != null)
unlinkCancelledWaiters();
// 7. 根据中断模式处理最终结果
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
核心子方法解析
-
addConditionWaiter:加入条件队列 创建状态为
CONDITION的节点,追加到条件队列尾部;如果发现尾节点已取消,先执行一次队列清理。private Node addConditionWaiter() {
Node t = lastWaiter;
// 尾节点已取消,先清理所有无效节点
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
// 新建节点,状态为CONDITION
Node node = new Node(Thread.currentThread(), Node.CONDITION);
// 队列为空则设为头节点,否则追加到尾部
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
2.fullyRelease:完全释放锁
因为 ReentrantLock 是可重入锁,线程可能多次获取锁(state>1),调用 await 时必须一次性释放全部同步状态,否则其他线程永远无法获取锁;同时记录释放前的 state 值,后续重新获锁时恢复重入次数。
ini
final int fullyRelease(Node node) {
boolean failed = true;
try {
int savedState = getState();
// 一次性释放全部state
if (release(savedState)) {
failed = false;
return savedState;
} else {
// 释放失败说明当前线程未持有锁,抛出非法监视器异常
throw new IllegalMonitorStateException();
}
} finally {
// 释放失败则标记节点为取消状态
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
5.3 signal () 唤醒流程源码解析
signal()的核心作用并不是直接唤醒线程让它运行,而是把满足条件的节点从条件队列,迁移到同步队列尾部,线程并不会立刻执行,它需要进入同步队列后,排队等待获取锁,才能真正恢复运行。
signal () 主流程
java
public final void signal() {
// 前置校验:当前线程必须持有锁
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
// 唤醒条件队列中第一个有效节点
doSignal(first);
}
doSignal:遍历唤醒首个有效节点
从条件队列头开始遍历,找到第一个未取消的节点,执行转移操作;如果节点已取消则继续往后找。
typescript
private void doSignal(Node first) {
do {
// 头节点后移,若后续无节点则尾指针置空
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
// 转移节点到同步队列,失败则继续处理下一个
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
transferForSignal:节点转移核心
这是双队列联动的核心方法,完成节点从条件队列到同步队列的迁移与状态转换:
arduino
final boolean transferForSignal(Node node) {
// 1. CAS修改状态:从CONDITION改为初始0
// 修改失败说明节点已被取消,直接返回false
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
// 2. 调用enq方法,将节点加入同步队列尾部,返回前驱节点
Node p = enq(node);
int ws = p.waitStatus;
// 3. 检查前驱节点状态:
// - 前驱已取消,或无法设置为SIGNAL,则直接唤醒当前线程
// - 让线程自己去同步队列中竞争并清理无效节点
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
这里有一个关键设计:signal () 不一定会立刻 unpark 线程。如果前驱节点状态正常且成功设为 SIGNAL,就不唤醒线程,等前驱节点释放锁时再统一唤醒,减少不必要的上下文切换。
5.4 双队列完整状态流转
结合 AQS 同步队列,节点的全生命周期流转对应线程的等待与唤醒全过程:
- 持有锁阶段:线程 A 成功获取锁,成为同步队列头节点对应的执行线程,state>0。
- 进入条件队列 :线程 A 调用
await(),创建 waitStatus=CONDITION 的节点,追加到条件队列尾部;同时调用fullyRelease完全释放锁,state 归零。 - 阻塞等待 :线程 A 执行
LockSupport.park()进入阻塞状态,此时节点仅存在于条件队列,脱离同步队列。 - 触发唤醒 :线程 B 获取锁后调用
signal(),取出条件队列头节点,CAS 将 waitStatus 从 CONDITION 改为 0,调用enq将节点加入同步队列尾部。 - 同步队列排队:节点进入同步队列后,遵循独占锁的排队规则:前驱节点设为 SIGNAL,线程继续阻塞等待。
- 重新获锁:前驱节点释放锁时唤醒该节点,线程竞争锁成功,重新设置 state 为之前的重入次数,从 await 方法返回,继续执行业务代码