Java 并发锁体系全解:从 synchronized、Lock 到 AQS 底层原理与源码深度剖析
在 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表示同步状态,这是整个同步器的核心状态变量:
private volatile int state;- 独占模式下:通常
state=0代表锁未被占用,state=1代表锁已被持有,可重入场景下 state 随重入次数递增 - 共享模式下:state 代表可用的共享资源总数
volatile保证了 state 在多线程间的可见性,AQS 同时提供compareAndSetState(int expect, int update)方法,基于 CAS 操作保证 state 修改的原子性,避免多线程并发修改的线程安全问题。
2.2 核心组件 2:FIFO 双向同步队列
当线程获取同步状态失败时,AQS 会将线程封装为Node节点,加入一个FIFO(先进先出)的双向链表同步队列,通过队列实现线程的等待调度与唤醒管理。
队列的节点为 AQS 的内部类Node,核心字段如下:
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()方法的底层核心:
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方法是状态流转的核心,负责调整前驱节点状态并判断当前线程是否可以安全阻塞:
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方法中:
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)是独占模式下释放同步状态的入口方法:
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方法:
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两个子类分别实现公平与非公平策略。
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 直接尝试抢一次锁,这是第一次插队机会。
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 抢锁,这是第二次插队机会。
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方法,严格遵守排队规则。
final void lock() { acquire(1); }tryAcquire:公平性核心,先判断队列
公平锁的tryAcquire与非公平锁的唯一区别,就是在锁空闲时,会先调用hasQueuedPredecessors()判断队列中是否有前驱等待线程,只有没有更早的等待者时,才尝试获取锁。
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
这是实现公平锁的关键方法,用于判断同步队列中是否存在比当前线程等待更久的线程。
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字段串联链表。
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 () 入口主流程
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 值,后续重新获锁时恢复重入次数。
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 () 主流程
public final void signal() { // 前置校验:当前线程必须持有锁 if (!isHeldExclusively()) throw new IllegalMonitorStateException(); Node first = firstWaiter; if (first != null) // 唤醒条件队列中第一个有效节点 doSignal(first); }doSignal:遍历唤醒首个有效节点
从条件队列头开始遍历,找到第一个未取消的节点,执行转移操作;如果节点已取消则继续往后找。
private void doSignal(Node first) { do { // 头节点后移,若后续无节点则尾指针置空 if ( (firstWaiter = first.nextWaiter) == null) lastWaiter = null; first.nextWaiter = null; // 转移节点到同步队列,失败则继续处理下一个 } while (!transferForSignal(first) && (first = firstWaiter) != null); }transferForSignal:节点转移核心
这是双队列联动的核心方法,完成节点从条件队列到同步队列的迁移与状态转换:
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 方法返回,继续执行业务代码