前言
在上一篇文章里面只是提到了AQS的大致框架,这篇主要来根据源码来刨析整个拿锁、排队、等待、挂起、唤醒、释放流程;
AQS:Abstract是抽象,抽象是贯穿整个java的,利用抽象来为上层实现框架,解决了很多问题;AQS是JUC锁底层基石,定义了一个框架来实现并发(利用CAS)
这个框架主要有三方面特点:① 要有通用性,下层实现透明的同步机制,同时与上层业务解耦 ② 利用CAS原子地修改共享标记位 ③ 如果有的线程必须要获取当前资源那么就必须要有一个等待队列
那么基于上一篇文章对AQS的了解来进行一下讲解
一、前置基础:Node结点与waitStatus关键状态
java
// waitStatus四种核心常量(独占只关注SIGNAL、CANCELLED)
static final int CANCELLED = 1; // 节点取消,线程放弃抢锁
static final int SIGNAL = -1; // 后继节点需要前驱唤醒(最核心)
static final int CONDITION = -2; // Condition条件队列使用
static final int PROPAGATE = -3; // 共享锁用
Node.prev/next:双向链表组成同步队列;thread:绑定排队线程;nextWaiter=EXCLUSIVE标记独占节点。state:int 同步状态,独占锁:state=0无锁,state>0持有锁(支持重入)。
二、独占锁获取完整过程:acquire入口
先来大致了解一下相关源码:

acquire(int arg):AQS 顶层入口,独占式获取锁(不可中断)acquireQueued(Node node,int arg):入队后自旋阻塞抢锁核心,interrupted 变量在这里定义parkAndCheckInterrupt():阻塞线程 + 返回本次 park 期间是否被中断selfInterrupt():线程补中断标记
acquire顶层源码
java
public final void acquire(int arg) {
// ①快速抢锁失败 && 入队自旋阻塞成功 → 等待期被中断,补中断
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
执行三段拆分:
tryAcquire快速抢锁 → addWaiter封装节点入队 → acquireQueued自旋阻塞排队。
(1) 阶段 1:tryAcquire (arg) 快速路径:非排队直接抢锁(子类实现)
AQS 空实现抛异常,由 ReentrantLock 公平 / 非公平 Sync 重写,成功返回 true 直接结束,失败进入入队逻辑。
非公平锁 tryAcquire 核心逻辑(NonfairSync)
state==0:CAS 把 state 设为 arg (1),设置exclusiveOwnerThread=当前线程,抢锁成功;state≠0 && 当前线程==持有锁线程:state+=arg,实现锁重入,返回 true;- 其他场景:抢锁失败返回 false,执行
addWaiter入队。
公平锁额外增加:同步队列有排队节点则直接返回 false,禁止插队,保证 FIFO。
结论:tryAcquire 是乐观快速路径,能拿到锁就不走队列,优化性能。
(2) 阶段 2:addWaiter (Node.EXCLUSIVE):抢锁失败→封装线程为 Node,插入同步队列尾部
java
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
// 队列已初始化:CAS快速入队尾
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 队列未初始化/快速CAS失败:enq自旋CAS初始化队列+入队
enq(node);
return node;
}
enq 队列初始化逻辑:
head==null:CAS 新建空哨兵 head 节点(哨兵节点不绑定任何线程,仅占位),head=tail;- 再次循环:把当前 node 挂在 tail 后,CAS 更新 tail,完成入队;
同步队列初始:
head(空哨兵) ↔ 排队节点1 ↔ 排队节点2 ↔ tail,只有 head 后继才有资格抢锁(FIFO)。
(3) 阶段 3:acquireQueued (final Node node, int arg):入队后自旋、阻塞、interrupted 变量核心逻辑(重点)
java
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true; // 标记是否异常取消节点(finally用)
try {
boolean interrupted = false; // 【核心变量】暂存阻塞期间中断状态
for (;;) { // 死循环自旋
final Node p = node.predecessor(); // 获取前驱节点
// 分支1:前驱是head哨兵 → 当前是队列首位,尝试抢锁
if (p == head && tryAcquire(arg)) {
setHead(node); // 当前节点晋升新head,原head出队
p.next = null; // 原head断开引用,帮助GC
failed = false;
return interrupted; // 抢到锁,返回中断标记
}
// 分支2:抢锁失败 → 校验前驱状态、决定是否park挂起
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
interrupted = true; // 被中断唤醒,仅标记,不跳出循环
}
} finally {
if (failed) // 代码异常跳出循环,取消节点
cancelAcquire(node);
}
}
代码详解:
① 这里的interrupted变量是非常重要的:
- 变量作用 :记录线程在
LockSupport.park()阻塞休眠阶段是否收到过中断信号; - 关键细节:
parkAndCheckInterrupt()内部Thread.interrupted():返回中断标记 + 清空线程原生中断位 ;- 若阻塞被中断唤醒 →
parkAndCheckInterrupt()=true → interrupted=true; - 正常被前驱 unpark 唤醒 → false,interrupted 不变;
- 若阻塞被中断唤醒 →
- AQS 不可中断语义:标记中断但不终止排队 : 哪怕中途被 interrupt,仅记录
interrupted=true,继续自旋抢锁;只有成功拿到锁后,才把标记返回上层 acquire。 - 上层
acquire拿到 true:执行selfInterrupt(),手动Thread.currentThread().interrupt(),补回被清空的中断位,上层业务代码可感知中断(AQS 不吞中断,延后处理)。
② shouldParkAfterFailedAcquire (p,node):挂起前置校验,修改前驱 waitStatus=SIGNAL (-1)
java
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
// 前驱已标记SIGNAL:前驱释放锁会唤醒自己,可以安心park
return true;
if (ws > 0) {
// 前驱CANCELLED(1):向前遍历,剔除所有取消节点,重新绑定前驱
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// ws=0:前驱无状态,CAS改成SIGNAL,告知前驱:你释放务必唤醒我
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false; // 本轮不park,下一轮循环再判断
}
SIGNAL 是 "预约唤醒":当前节点要睡觉前,必须让前驱打上 SIGNAL,保证释放锁时一定会唤醒后继;第一次 CAS 改状态返回 false 不阻塞,第二次循环 ws=SIGNAL 返回 true 进入 park。
③ parkAndCheckInterrupt ():真正挂起线程,等待唤醒
java
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this); // 线程进入WAITING阻塞,两种唤醒:unpark/中断
return Thread.interrupted();
}
- 正常唤醒:前驱释放锁
unparkSuccessor→park 结束,返回 false; - 中断唤醒:其他线程调用
thread.interrupt()唤醒,返回 true,interrupted 置 true 继续自旋抢锁。
④ 自旋两种结束场景
- 正常:轮到自己(前驱 = head)+tryAcquire 成功→晋升 head,返回 interrupted;
- 异常:try 代码抛异常→failed 保持 true→finally 执行
cancelAcquire取消排队节点。
(4)阶段4:selfInterrupt ():抢到锁后补中断
java
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
- 若
acquireQueued返回true:说明阻塞等待期间被中断,但线程已经成功抢到锁; Thread.interrupted()在 park 时已经清空了中断标记 ,因此需要selfInterrupt()重新设置中断位,把之前被吞掉的中断补上;- 上层业务代码后续调用
Thread.isInterrupted()就能感知到中断。
AQS独占加锁的流程图如下:

三、独占锁释放完整流程
release顶层源码(释放锁统一入口)
java
public final boolean release(int arg) {
// ①子类实现tryRelease:锁完全释放返回true,部分重入释放返回false
if (tryRelease(arg)) {
Node h = head;
// head非空&&waitStatus≠0(有等待唤醒的后继)→唤醒head后继
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
(1)阶段1:tryRelease (arg):子类实现,释放同步状态(ReentrantLock Sync)
java
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
// 非法释放:非持有锁线程抛异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) { // state归零,锁完全释放
free = true;
setExclusiveOwnerThread(null);
}
setState(c); // 无并发竞争,无需CAS
return free;
}
- 重入场景:连续 lock 两次,state=2,第一次 unlock→c=1≠0→free=false,不唤醒队列;
- 完全释放:c=0→free=true,进入
unparkSuccessor唤醒 head 后继排队线程。
(2)阶段 2:unparkSuccessor (Node head):唤醒 head 后面第一个有效等待节点
java
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
// 把head状态从SIGNAL改成0
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
// 后继取消/为空:从tail倒着找离head最近的非CANCELLED节点
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); // 唤醒目标线程
}
这里为什么不直接看head后面的节点,而是倒序查找?
节点入队是先改 prev 再 CAS 改 tail,next 指针并发下可能断裂,prev 指针稳定,从后往前找避免漏找有效节点;唤醒后被阻塞线程从park()处恢复,回到 acquireQueued 死循环继续抢锁。
四、全流程
这里将整个过程用一个例子来帮大家顺一遍,更加清晰:T0持有锁、T1/T2排队
- T0 执行 lock () :
tryAcquire(1)CAS 修改 state=1,owner=T0,直接拿到锁,无入队; - T1 执行 lock () :tryAcquire 失败→addWaiter 创建 EXCLUSIVE 节点,队列初始化 head (空)→T1 节点入队 tail→acquireQueued 自旋;
- T1 前驱 = head≠head(初次)→shouldPark 改 head.ws=SIGNAL (-1),本轮不 park;
- 第二轮循环 ws=SIGNAL→parkAndCheckInterrupt,T1 进入 WAITING 阻塞;interrupted=false;
- T2 执行 lock ():tryAcquire 失败→入队 tail,前驱 = T1→shouldPark 把 T1.ws 改成 SIGNAL,T2 后续 park 阻塞; 队列:head (-1) ↔ T1 (-1) ↔ T2 (0)(最终 T2 被挂起);
- T0 调用 unlock ()→release (1):tryRelease→state=0、owner=null,free=true;head≠null&&ws=-1→unparkSuccessor 唤醒 T1;
- T1 被 unpark 唤醒:跳出 park,回到 acquireQueued 循环,p=head→tryAcquire 成功,state=1,T1 晋升新 head,原 head.next=null 被 GC;返回 interrupted=false→上层 acquire 不执行 selfInterrupt;
- T1 后续 unlock:同理唤醒 T2,T2 开始抢锁。