AQS源码讲解
前言
AbstractQueuedSynchronizer(AQS)是Java并发包中最核心的基础框架,它为实现阻塞锁和相关同步器提供了一个框架。本文将从ReentrantLock的构造过程开始,逐步深入分析lock和unlock的完整实现机制,揭示Java并发编程的底层奥秘。
1. ReentrantLock构造过程深度解析
1.1 继承关系图
classDiagram
class Lock {
<<interface>>
+lock()
+unlock()
+tryLock()
+newCondition()
}
class ReentrantLock {
-Sync sync
+ReentrantLock()
+ReentrantLock(boolean fair)
+lock()
+unlock()
}
class Sync {
<<abstract>>
+lock()
+tryRelease(int)
+isHeldExclusively()
}
class FairSync {
+initialTryLock()
+tryAcquire(int)
}
class NonfairSync {
+initialTryLock()
+tryAcquire(int)
}
class AbstractQueuedSynchronizer {
-volatile int state
-volatile Node head
-volatile Node tail
+acquire(int)
+release(int)
+getState()
+setState(int)
+compareAndSetState(int, int)
}
class AbstractOwnableSynchronizer {
-Thread exclusiveOwnerThread
+setExclusiveOwnerThread(Thread)
+getExclusiveOwnerThread()
}
Lock <|.. ReentrantLock
ReentrantLock *-- Sync
Sync <|-- FairSync
Sync <|-- NonfairSync
AbstractQueuedSynchronizer <|-- Sync
AbstractOwnableSynchronizer <|-- AbstractQueuedSynchronizer
1.2 ReentrantLock构造函数源码分析
public class ReentrantLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = 7373984872572414699L;
/** 同步器,提供所有实现机制 */
private final Sync sync;
/**
* 默认构造函数,创建非公平锁
* 非公平锁性能更好,但可能导致线程饥饿
*
* 选择非公平锁的原因:
* 1. 更高的吞吐量:减少线程切换开销
* 2. 更低的延迟:新线程可能立即获取锁
* 3. 更好的缓存局部性:刚释放锁的线程可能很快重新获取
*/
public ReentrantLock() {
sync = new NonfairSync();
}
/**
* 创建ReentrantLock实例
* @param fair true表示使用公平锁,false表示非公平锁
*
* 公平锁 vs 非公平锁:
* - 公平锁:严格按照FIFO顺序,保证公平性,但性能较低
* - 非公平锁:允许"插队",性能更高,但可能导致线程饥饿
*/
public ReentrantLock(boolean fair) {
// 根据参数选择创建公平锁或非公平锁的同步器
// 这是策略模式的典型应用
sync = fair ? new FairSync() : new NonfairSync();
}
}
1.3 Sync基类 - 连接ReentrantLock与AQS的桥梁
/**
* ReentrantLock的同步器基类,继承自AQS
* 使用AQS的state字段表示锁的持有次数,支持重入
* 这是模板方法模式的经典应用:AQS定义框架,Sync实现具体逻辑
*/
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
/**
* 抽象方法,由子类实现具体的初始获取逻辑
* 公平锁和非公平锁的核心区别就在这里
*/
abstract boolean initialTryLock();
/**
* 锁的获取入口,模板方法模式
* 先尝试快速获取,失败则进入AQS的完整流程
*/
@ReservedStackAccess
final void lock() {
if (!initialTryLock()) // 快速尝试获取锁
acquire(1); // 进入AQS的完整获取流程
}
/**
* 非公平的tryLock实现
* 不管队列状态,直接尝试获取锁
*/
@ReservedStackAccess
final boolean tryLock() {
Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 锁空闲,直接CAS获取
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(current);
return true;
}
} else if (getExclusiveOwnerThread() == current) {
// 重入逻辑:当前线程已持有锁
if (++c < 0) // 检查重入次数溢出
throw new Error("Maximum lock count exceeded");
setState(c);
return true;
}
return false;
}
/**
* 释放锁的实现,支持重入锁的递减逻辑
* 只有当重入计数减到0时,锁才真正被释放
*/
@ReservedStackAccess
protected final boolean tryRelease(int releases) {
int c = getState() - releases; // 计算释放后的状态值
// 安全检查:只有锁的拥有者才能释放锁
if (getExclusiveOwnerThread() != Thread.currentThread())
throw new IllegalMonitorStateException();
boolean free = (c == 0); // 判断是否完全释放
if (free) {
// 只有完全释放时才清除拥有者线程
setExclusiveOwnerThread(null);
}
setState(c); // 更新状态
return free; // 返回是否完全释放
}
// 其他辅助方法
protected final boolean isHeldExclusively() {
return getExclusiveOwnerThread() == Thread.currentThread();
}
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}
final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
}
final boolean isLocked() {
return getState() != 0;
}
}
1.4 FairSync - 公平锁实现
/**
* 公平锁实现
* 严格按照FIFO顺序,新线程必须排队等待
* 优势:避免线程饥饿,保证等待时间的公平性
* 劣势:性能较低,因为需要维护严格的队列顺序
*/
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
/**
* 公平锁的初始尝试获取锁
* 与非公平锁的关键区别:必须检查等待队列
*/
final boolean initialTryLock() {
Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 公平锁的核心:检查是否有其他线程在等待
// hasQueuedThreads()检查等待队列是否为空
if (!hasQueuedThreads() && compareAndSetState(0, 1)) {
setExclusiveOwnerThread(current);
return true;
}
} else if (getExclusiveOwnerThread() == current) {
// 重入逻辑:即使是公平锁,重入也是允许的
if (++c < 0)
throw new Error("Maximum lock count exceeded");
setState(c);
return true;
}
return false;
}
/**
* 公平锁的tryAcquire实现
* 与非公平锁的关键区别:必须检查是否有前驱等待者
*/
protected final boolean tryAcquire(int acquires) {
if (getState() == 0 && !hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
}
1.5 AbstractQueuedSynchronizer核心字段
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
/**
* 等待队列的头节点,延迟初始化
* 头节点通常代表当前持有锁的线程(或空节点)
*/
private transient volatile Node head;
/**
* 等待队列的尾节点,延迟初始化
* 新的等待线程总是被添加到队列尾部
*/
private transient volatile Node tail;
/**
* 同步状态,这是AQS的核心
* 对于ReentrantLock:0表示未锁定,>0表示锁定次数(支持重入)
* 使用volatile保证可见性,通过CAS保证原子性
*/
private volatile int state;
/**
* 获取同步状态
*/
protected final int getState() {
return state;
}
/**
* 设置同步状态
*/
protected final void setState(int newState) {
state = newState;
}
/**
* CAS更新同步状态
* 这是AQS中最关键的原子操作
*/
protected final boolean compareAndSetState(int expect, int update) {
return STATE.compareAndSet(this, expect, update);
}
}
2. ReentrantLock.lock()过程深度解析
2.1 lock()调用链路图
sequenceDiagram
participant Client
participant ReentrantLock
participant FairSync
participant AQS
participant Node
participant LockSupport
Client->>ReentrantLock: lock()
ReentrantLock->>FairSync: sync.lock()
FairSync->>FairSync: initialTryLock()
alt 快速获取成功
FairSync-->>ReentrantLock: 获取成功
ReentrantLock-->>Client: 返回
else 快速获取失败
FairSync->>AQS: acquire(1)
AQS->>FairSync: tryAcquire(1)
alt tryAcquire成功
FairSync-->>AQS: true
AQS-->>FairSync: 获取成功
else tryAcquire失败
AQS->>Node: 创建节点
AQS->>AQS: 入队操作
AQS->>LockSupport: park()
Note over LockSupport: 线程阻塞等待
LockSupport-->>AQS: 被唤醒
AQS->>AQS: 重新尝试获取
end
end
2.2 第一阶段:ReentrantLock.lock()入口
/**
* ReentrantLock的lock方法
* 这是用户调用的入口点
*/
public void lock() {
sync.lock(); // 委托给同步器处理
}
2.3 第二阶段:Sync.lock()模板方法
/**
* Sync的lock方法,模板方法模式
* 定义了获取锁的标准流程
*/
@ReservedStackAccess
final void lock() {
// 第一步:快速路径,尝试立即获取锁
if (!initialTryLock())
// 第二步:慢速路径,进入AQS的完整获取流程
acquire(1);
}
2.4 第三阶段:FairSync.initialTryLock()快速获取
/**
* 公平锁的快速获取尝试
* 这是性能优化的关键:避免不必要的线程阻塞
*/
final boolean initialTryLock() {
Thread current = Thread.currentThread();
int c = getState(); // 获取当前同步状态
if (c == 0) { // 锁完全空闲
// 公平锁关键检查:队列是否为空
if (!hasQueuedThreads() && compareAndSetState(0, 1)) {
setExclusiveOwnerThread(current);
return true; // 快速获取成功
}
} else if (getExclusiveOwnerThread() == current) {
// 重入情况:当前线程已持有锁
if (++c < 0)
throw new Error("Maximum lock count exceeded");
setState(c); // 增加重入计数
return true;
}
return false; // 快速获取失败
}
hasQueuedThreads方法详解
从 tail 开始遍历
-
目的:安全地检查整个队列是否存在有效节点。
-
原因:
- 尾部更新是原子的:入队操作通过 CAS 更新
tail指针,保证tail始终指向最新节点。 - 避免并发入队干扰:从
tail向head遍历时,新节点只会添加到尾部,不影响正向遍历的稳定性。 - 规避 next 指针延迟:节点入队时先设置
prev指针,再设置next指针。反向遍历依赖prev,能更快看到新节点。
- 尾部更新是原子的:入队操作通过 CAS 更新
/**
* 检查是否有线程在等待队列中排队
* 这是公平锁判断是否需要排队的关键方法
*
* 实现原理:
* 1. 从tail向head遍历队列
* 2. 检查每个节点的status是否>=0(非取消状态)
* 3. 如果找到任何有效等待节点,返回true
*
* 性能优化:
* - 使用局部变量缓存head和tail,减少volatile读取
* - 短路求值:一旦找到有效节点立即返回
*/
public final boolean hasQueuedThreads() {
// 从尾部开始遍历,直到头部或遇到null
for (Node p = tail, h = head; p != h && p != null; p = p.prev) {
// status >= 0 表示节点未被取消
// CANCELLED = 0x80000000 是负数
// WAITING = 1, 其他正常状态都是非负数
if (p.status >= 0)
return true; // 发现有效等待节点
}
return false; // 队列为空或所有节点都已取消
}
2.5 第四阶段:AQS.acquire()完整获取流程
/**
* AQS的acquire方法 - 独占模式获取的入口
* 这是所有独占锁获取的统一入口
*
* 设计思路:
* 1. 先给子类一次快速获取的机会(tryAcquire)
* 2. 如果失败,进入完整的排队等待流程
* 3. 这种两阶段设计平衡了性能和公平性
*/
public final void acquire(int arg) {
// 阶段1:快速尝试获取锁(给子类第二次机会)
// 这里的tryAcquire与initialTryLock可能有不同的实现策略
if (!tryAcquire(arg))
// 阶段2:进入完整的获取流程:创建节点、排队、阻塞、唤醒
// 参数说明:node=null, arg=1, shared=false, interruptible=false, timed=false, time=0L
acquire(null, arg, false, false, false, 0L);
}
2.6 第五阶段:FairSync.tryAcquire()第二次尝试
/**
* 公平锁的第二次获取尝试
* 在即将进入队列前的最后一次尝试
*/
protected final boolean tryAcquire(int acquires) {
// 双重检查:锁空闲 && 没有前驱等待者
if (getState() == 0 && !hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
hasQueuedPredecessors方法详解
从 head 开始遍历
-
目的:高效获取第一个有效节点(
head.next)。 -
原因:
-
多数情况下,
head.next能直接定位到第一个等待线程。 -
但需处理三种并发异常(见代码注释):
s == null:节点正被移除。first == null:节点未完全初始化(waiter未赋值)。s.prev == null:prev指针未正确链接。
-
快速路径失败时,需降级到慢速路径(从 tail 遍历) 保证正确性。
-
/**
* 检查是否有其他线程比当前线程更早地在队列中等待
* 这是公平锁实现FIFO顺序的核心方法
*
* 设计思路:
* 1. 快速路径:直接检查head.next节点
* 2. 慢速路径:如果快速检查失败,通过getFirstQueuedThread确认
* 3. 最终判断:第一个等待线程是否为当前线程
*
* 返回值含义:
* - true: 有其他线程在当前线程之前等待,当前线程应该排队
* - false: 当前线程可以尝试获取锁(队列为空或当前线程是第一个)
*/
public final boolean hasQueuedPredecessors() {
Thread first = null;
Node h, s;
// 快速路径检查:head -> head.next 链路
if ((h = head) != null &&
((s = h.next) == null || // head.next为null(队列正在变化)
(first = s.waiter) == null || // head.next.waiter为null(节点未完全初始化)
s.prev == null)) { // head.next.prev为null(节点正在入队)
// 快速路径失败,使用慢速路径确认
// 这种情况通常发生在并发修改队列时
first = getFirstQueuedThread();
}
// 判断第一个等待线程是否为当前线程
// first != null: 队列中有等待线程
// first != Thread.currentThread(): 第一个等待线程不是当前线程
return first != null && first != Thread.currentThread();
}
getFirstQueuedThread方法详解
/**
* 获取队列中第一个(等待时间最长的)线程
* 这是hasQueuedPredecessors的后备方法
*
* 实现策略:
* 1. 优先使用head.next快速获取
* 2. 如果快速路径失败,从tail向前遍历
*
* 为什么需要从tail遍历:
* - 入队是原子的(CAS tail),但设置next链接不是
* - 在并发环境下,head.next可能暂时不可用
* - 从tail向前遍历能保证找到真正的第一个等待线程
*/
public final Thread getFirstQueuedThread() {
Thread first = null, w;
Node h, s;
// 尝试快速路径:head -> head.next
if ((h = head) != null &&
((s = h.next) == null || // next链接未建立
(first = s.waiter) == null || // waiter未设置
s.prev == null)) { // prev链接异常
// 快速路径失败,使用慢速路径
// 从tail开始向前遍历,找到最前面的有效节点
for (Node p = tail, q; p != null && (q = p.prev) != null; p = q) {
if ((w = p.waiter) != null)
first = w; // 不断更新first,最终得到最前面的线程
}
}
return first;
}
2.7 第六阶段:AQS核心acquire方法详解
这是AQS中最复杂也是最核心的方法,实现了完整的线程同步机制。让我们逐行分析其精妙的设计:
2.7.1 acquire方法
/**
* AQS的核心acquire方法 - 完整版本
* 这个方法是整个AQS框架的心脏,实现了:
* 1. 线程安全的队列管理
* 2. 高效的自旋等待机制
* 3. 精确的线程阻塞和唤醒
* 4. 完善的异常和中断处理
*
* @param node 当前线程的节点(初始为null,延迟创建)
* @param arg 获取参数(对于ReentrantLock通常是1)
* @param shared 是否为共享模式(false表示独占模式)
* @param interruptible 是否响应中断
* @param timed 是否有超时限制
* @param time 超时时间(纳秒)
* @return 1表示成功,负数表示失败或中断
*/
final int acquire(Node node, int arg, boolean shared,
boolean interruptible, boolean timed, long time) {
// 获取当前线程引用,避免重复调用
Thread current = Thread.currentThread();
// 自旋计数器:控制自旋次数,避免过度消耗CPU
byte spins = 0, postSpins = 0;
// 状态标记
boolean interrupted = false; // 是否被中断过
boolean first = false; // 是否是队列中第一个等待者
// 前驱节点引用,用于队列遍历和状态检查
Node pred = null;
/*
* 核心无限循环:实现状态机驱动的获取逻辑
*
* 状态转换路径:
* 1. 初始状态 -> 检查获取资格
* 2. 有资格 -> 尝试获取锁
* 3. 获取成功 -> 更新队列并返回
* 4. 获取失败 -> 创建/入队节点
* 5. 节点入队 -> 自旋等待
* 6. 自旋结束 -> 设置等待状态
* 7. 状态设置 -> 阻塞等待
* 8. 被唤醒 -> 回到步骤1
*
* 这种设计的优势:
* - 无锁化:整个过程不需要额外的锁保护
* - 高效性:通过自旋减少线程切换
* - 公平性:严格按照FIFO顺序
* - 健壮性:完善的异常和边界情况处理
*/
for (;;) {
// =================== 阶段1:获取资格检查 ===================
/*
* 这个阶段决定当前线程是否有资格尝试获取锁
* 核心逻辑:只有队列中的第一个等待者才能尝试获取锁
*
* 条件分析:
* !first: 当前不是第一个等待者
* pred = node.prev: 获取前驱节点
* !(first = (head == pred)): 检查前驱是否为头节点
*/
if (!first && (pred = (node == null) ? null : node.prev) != null &&
!(first = (head == pred))) {
// 前驱节点已被取消,需要清理队列
if (pred.status < 0) {
cleanQueue(); // 批量清理取消的节点
continue; // 重新开始循环
}
// 前驱节点正在入队过程中(prev链接未完成)
else if (pred.prev == null) {
Thread.onSpinWait(); // CPU友好的自旋等待
continue; // 等待前驱完成入队
}
}
// =================== 阶段2:尝试获取锁 ===================
/*
* 只有满足以下条件之一才能尝试获取:
* 1. first = true: 是队列中第一个等待者
* 2. pred == null: 还没有入队(首次尝试)
*
* 这种设计保证了公平性:只有轮到的线程才能获取锁
*/
if (first || pred == null) {
boolean acquired;
try {
// 调用子类实现的获取逻辑
// 对于ReentrantLock,这里会调用FairSync.tryAcquire()
acquired = tryAcquire(arg);
} catch (Throwable ex) {
// 异常处理:取消当前节点并重新抛出异常
cancelAcquire(node, interrupted, false);
throw ex;
}
if (acquired) {
// ============ 获取成功的后续处理 ============
if (first) {
/*
* 更新队列头节点:
* 1. 将当前节点设为新的头节点
* 2. 清理节点的等待信息
* 3. 断开与前驱的链接
* 4. 帮助GC回收旧的头节点
*/
node.prev = null; // 断开前驱链接
head = node; // 更新头节点
pred.next = null; // 断开旧头节点的后继链接
node.waiter = null; // 清除等待线程(头节点不需要)
// 如果在等待过程中被中断过,恢复中断状态
if (interrupted)
current.interrupt();
}
return 1; // 成功获取,返回正数
}
}
// =================== 阶段3:节点创建和入队 ===================
// 子阶段3.1:延迟创建节点
if (node == null) {
/*
* 延迟创建策略:
* 只有在确实需要等待时才创建节点
* 这避免了不必要的对象分配,提高了快速路径的性能
*/
if (shared)
node = new SharedNode(); // 共享模式节点
else
node = new ExclusiveNode(); // 独占模式节点(ReentrantLock使用)
}
// 子阶段3.2:节点入队操作
else if (pred == null) {
/*
* 入队操作的精妙设计:
* 1. 先设置waiter,确保节点有效
* 2. 使用Relaxed模式设置prev,减少内存屏障开销
* 3. CAS更新tail,保证原子性
* 4. 成功后设置next链接,完成双向链表
*/
node.waiter = current; // 设置等待线程
Node t = tail; // 获取当前尾节点
node.setPrevRelaxed(t); // 设置前驱(Relaxed模式)
if (t == null) {
// 队列为空,需要初始化头节点
tryInitializeHead();
} else if (!casTail(t, node)) {
// CAS失败,说明有其他线程在并发入队
node.setPrevRelaxed(null); // 重置prev,准备重试
} else {
// CAS成功,完成入队
t.next = node; // 设置前驱的next链接
}
}
// =================== 阶段4:自旋优化 ===================
/*
* 自旋策略:
* 对于队列中第一个等待者,进行短暂自旋
* 目的:如果锁很快被释放,可以避免线程阻塞的开销
*
* 自旋条件:
* 1. first = true: 是第一个等待者
* 2. spins != 0: 还有剩余自旋次数
*/
else if (first && spins != 0) {
--spins; // 减少自旋计数
Thread.onSpinWait(); // CPU友好的自旋指令
/*
* Thread.onSpinWait()的作用:
* 1. 向CPU发出自旋等待的提示
* 2. 在某些架构上可以降低功耗
* 3. 避免流水线停顿,提高性能
*/
}
// =================== 阶段5:设置等待状态 ===================
/*
* 状态设置策略:
* 在阻塞线程之前,必须先设置节点状态为WAITING
* 这样其他线程在释放锁时才知道需要唤醒这个节点
*
* 状态值含义:
* 0: 初始状态或已取消
* WAITING(1): 需要被唤醒
* CANCELLED(负数): 已取消
*/
else if (node.status == 0) {
node.status = WAITING; // 设置为等待状态
/*
* 为什么不直接在创建节点时设置WAITING?
* 1. 避免在自旋阶段被误唤醒
* 2. 确保只有真正需要阻塞的节点才设置WAITING
* 3. 简化状态管理逻辑
*/
}
// =================== 阶段6:线程阻塞 ===================
/*
* 最终阶段:阻塞当前线程等待唤醒
* 这是性能的关键点,需要精确控制阻塞和唤醒
*/
else {
long nanos;
// 更新自旋计数器(用于被唤醒后的自旋)
spins = postSpins = (byte)((postSpins << 1) | 1);
if (!timed) {
// 无超时限制:无限期阻塞
LockSupport.park(this);
/*
* LockSupport.park()的特点:
* 1. 比Object.wait()更高效
* 2. 不需要获取监视器锁
* 3. 支持精确的线程唤醒
* 4. 可以被unpark()提前唤醒
*/
} else if ((nanos = time - System.nanoTime()) > 0L) {
// 有超时限制:定时阻塞
LockSupport.parkNanos(this, nanos);
} else {
// 超时,退出循环
break;
}
// 被唤醒后的处理
node.clearStatus(); // 清除WAITING状态
// 检查中断状态
if ((interrupted |= Thread.interrupted()) && interruptible) {
break; // 响应中断,退出循环
}
/*
* 线程被唤醒的可能原因:
* 1. 前驱节点释放了锁(正常情况)
* 2. 被中断
* 3. 超时
* 4. 虚假唤醒(spurious wakeup)
*
* 无论哪种情况,都会重新进入循环,
* 重新检查获取条件
*/
}
} // for循环结束
// 如果到达这里,说明获取失败(中断或超时)
return cancelAcquire(node, interrupted, interruptible);
}
2.7.9 设计精髓总结
这个方法的设计体现了以下几个重要思想:
- 状态机驱动:通过无限循环实现状态转换,每个阶段都有明确的职责
- 延迟初始化:节点和队列都是延迟创建,提高快速路径性能
- 自旋优化:通过适度自旋减少线程切换开销
- 内存屏障优化:使用Relaxed模式减少不必要的内存屏障
- 异常安全:完善的异常处理和资源清理机制
- 公平性保证:严格的FIFO顺序,避免线程饥饿
- 性能平衡:在公平性和性能之间找到最佳平衡点
2.8 AQS内存结构图
graph TB
subgraph "AQS内存结构"
AQS["AbstractQueuedSynchronizer<br/>state: int (锁状态)<br/>head: Node (队列头)<br/>tail: Node (队列尾)"]
subgraph "CLH双向链表队列"
HEAD["Head Node (虚拟节点)<br/>status: 0<br/>waiter: null<br/>prev: null<br/>next: Node1"]
NODE1["Node1<br/>status: WAITING(1)<br/>waiter: Thread1<br/>prev: head<br/>next: Node2"]
NODE2["Node2<br/>status: WAITING(1)<br/>waiter: Thread2<br/>prev: Node1<br/>next: Node3"]
NODE3["Node3 (Tail)<br/>status: WAITING(1)<br/>waiter: Thread3<br/>prev: Node2<br/>next: null"]
HEAD -->|next| NODE1
NODE1 -->|next| NODE2
NODE2 -->|next| NODE3
NODE1 -.->|prev| HEAD
NODE2 -.->|prev| NODE1
NODE3 -.->|prev| NODE2
end
AQS -->|head指针| HEAD
AQS -->|tail指针| NODE3
end
subgraph "状态说明"
STATE1["state = 0: 锁空闲"]
STATE2["state > 0: 锁被持有,值为重入次数"]
STATUS1["WAITING = 1: 需要被唤醒"]
STATUS2["CANCELLED = 0x80000000: 已取消"]
end
subgraph "队列特性"
FEATURE1["✓ 双向链表:支持正向和反向遍历"]
FEATURE2["✓ FIFO顺序:先入队的线程先被唤醒"]
FEATURE3["✓ 虚拟头节点:简化边界条件处理"]
FEATURE4["✓ 原子操作:CAS保证线程安全"]
end
style AQS fill:#e3f2fd
style HEAD fill:#f5f5f5
style NODE1 fill:#ffecb3
style NODE2 fill:#ffecb3
style NODE3 fill:#ffcdd2
style STATE1 fill:#e8f5e8
style STATE2 fill:#e8f5e8
style STATUS1 fill:#fff3e0
style STATUS2 fill:#fff3e0
style FEATURE1 fill:#f3e5f5
style FEATURE2 fill:#f3e5f5
style FEATURE3 fill:#f3e5f5
style FEATURE4 fill:#f3e5f5
3. ReentrantLock.unlock()过程深度解析
3.1 unlock()调用链路图
sequenceDiagram
participant Client
participant ReentrantLock
participant Sync
participant AQS
participant Node
participant LockSupport
Client->>ReentrantLock: unlock()
ReentrantLock->>AQS: sync.release(1)
AQS->>Sync: tryRelease(1)
alt 重入情况(state > 1)
Sync->>Sync: state--
Sync-->>AQS: false (未完全释放)
AQS-->>ReentrantLock: 完成
else 完全释放(state == 1)
Sync->>Sync: state = 0
Sync->>Sync: setExclusiveOwnerThread(null)
Sync-->>AQS: true (完全释放)
AQS->>AQS: signalNext(head)
AQS->>Node: 找到后继节点
AQS->>Node: 清除WAITING状态
AQS->>LockSupport: unpark(successor.waiter)
Note over LockSupport: 唤醒等待线程
LockSupport-->>Node: 线程被唤醒
Node->>AQS: 重新进入acquire循环
end
3.2 第一阶段:ReentrantLock.unlock()入口
/**
* ReentrantLock的unlock方法
* 用户调用的解锁入口点
*/
public void unlock() {
sync.release(1); // 委托给AQS的release方法
}
3.3 第二阶段:AQS.release()释放流程
/**
* AQS的release方法 - 独占模式释放
* 如果tryRelease返回true,则唤醒等待队列中的下一个线程
*
* 执行流程:
* 1. 调用子类的tryRelease尝试释放锁
* 2. 如果完全释放(重入计数为0),唤醒等待队列中的下一个线程
* 3. 如果未完全释放(重入计数>0),直接返回false
*
* 重入锁的释放特点:
* - 每次unlock只减少1个重入计数
* - 只有当计数减到0时才真正释放锁
* - 只有真正释放时才会唤醒其他等待线程
*/
public final boolean release(int arg) {
// 阶段1:尝试释放锁(处理重入计数)
if (tryRelease(arg)) {
// 阶段2:锁完全释放,唤醒等待队列中的下一个线程
// 从头节点开始查找并唤醒第一个有效的等待节点
signalNext(head);
return true; // 释放成功
}
return false; // 锁未完全释放(重入情况,计数>0)
}
3.4 第三阶段:Sync.tryRelease()释放逻辑
/**
* 释放锁的核心实现
* 处理重入锁的递减逻辑
*/
@ReservedStackAccess
protected final boolean tryRelease(int releases) {
int c = getState() - releases; // 计算释放后的状态
// 安全检查:只有锁的拥有者才能释放
if (getExclusiveOwnerThread() != Thread.currentThread())
throw new IllegalMonitorStateException();
boolean free = (c == 0); // 判断是否完全释放
if (free) {
// 只有完全释放时才清除拥有者线程
// 这确保了重入情况下线程仍然是锁的拥有者
setExclusiveOwnerThread(null);
}
setState(c); // 更新状态(不需要CAS,因为只有拥有者能调用)
return free; // 返回是否完全释放
}
3.5 第四阶段:AQS.signalNext()唤醒后继
/**
* 唤醒后继节点的核心方法
* 实现了精确的线程唤醒机制,避免"惊群效应"
*
* 设计要点:
* 1. 只唤醒直接后继节点,不是广播唤醒
* 2. 使用原子操作确保状态更新的线程安全性
* 3. 通过LockSupport实现精确的线程唤醒
*
* 状态检查逻辑:
* - status = 0: 节点已被取消或正常状态,无需唤醒
* - status = WAITING(1): 节点正在等待,需要被唤醒
* - status = CANCELLED(负数): 节点已取消,无需唤醒
*/
static void signalNext(Node h) {
Node s;
// 三重检查确保唤醒的有效性:
if (h != null && // 1. 头节点存在
(s = h.next) != null && // 2. 后继节点存在
s.status != 0) { // 3. 后继节点需要被唤醒(status != 0)
// 阶段1:原子性地清除WAITING状态,防止重复唤醒
// getAndUnsetStatus是原子操作,返回旧值并清除指定位
s.getAndUnsetStatus(WAITING);
// 阶段2:唤醒后继节点中的等待线程
// LockSupport.unpark是线程唤醒的底层原语
LockSupport.unpark(s.waiter);
}
}
3.6 unlock过程状态变化图
stateDiagram-v2
[*] --> 持有锁状态
持有锁状态 : state > 0
持有锁状态 : exclusiveOwnerThread = currentThread
持有锁状态 --> 检查重入: unlock()调用
检查重入 --> 减少重入计数: state > 1 (重入情况)
检查重入 --> 完全释放: state == 1 (最后一次释放)
减少重入计数 : state--
减少重入计数 : 保持exclusiveOwnerThread
减少重入计数 --> 持有锁状态: 继续持有锁
完全释放 : state = 0
完全释放 : exclusiveOwnerThread = null
完全释放 --> 唤醒后继: signalNext(head)
唤醒后继 --> 锁空闲状态: 唤醒完成
锁空闲状态 : state = 0
锁空闲状态 : 等待新的获取者
锁空闲状态 --> [*]
4. 线程竞争场景模拟 - AQS状态变化全过程
为了更好地理解AQS的工作机制,我们来模拟一个典型的多线程竞争场景:
- 线程A首先获取锁
- 线程B尝试获取锁(被阻塞)
- 线程A释放锁
- 线程B被唤醒并获取锁
4.1 初始状态
graph TB
subgraph "AQS初始状态"
AQS1["AQS<br/>state: 0<br/>head: null<br/>tail: null<br/>exclusiveOwnerThread: null"]
end
subgraph "线程状态"
ThreadA["线程A: 准备lock()"]
ThreadB["线程B: 准备lock()"]
end
style AQS1 fill:#e1f5fe
style ThreadA fill:#fff3e0
style ThreadB fill:#fff3e0
4.2 阶段1:线程A获取锁成功
执行流程:
- 线程A调用
lock.lock() - 进入
FairSync.initialTryLock() getState()返回 0(锁空闲)hasQueuedThreads()返回 false(队列为空)compareAndSetState(0, 1)成功setExclusiveOwnerThread(ThreadA)
graph TB
subgraph "AQS状态 - 线程A获取锁后"
AQS2["AQS<br/>state: 1<br/>head: null<br/>tail: null<br/>exclusiveOwnerThread: ThreadA"]
end
subgraph "线程状态"
ThreadA2["线程A: 持有锁,执行临界区"]
ThreadB2["线程B: 准备lock()"]
end
style AQS2 fill:#c8e6c9
style ThreadA2 fill:#4caf50,color:#fff
style ThreadB2 fill:#fff3e0
4.3 阶段2:线程B尝试获取锁,进入等待队列
执行流程:
- 线程B调用
lock.lock() - 进入
FairSync.initialTryLock() getState()返回 1(锁被占用)getExclusiveOwnerThread() != ThreadB,快速获取失败- 进入
AQS.acquire(1) FairSync.tryAcquire(1)失败(锁被占用)- 创建Node节点,入队操作
- 线程B被
LockSupport.park()阻塞
graph LR
subgraph "AQS状态 - 线程B入队后"
AQS3["AQS<br/>state: 1<br/>exclusiveOwnerThread: ThreadA"]
subgraph "CLH队列"
HEAD3["Head Node<br/>status: 0<br/>waiter: null<br/>prev: null"]
NODE3["Node (ThreadB)<br/>status: WAITING(1)<br/>waiter: ThreadB<br/>prev: head<br/>next: null"]
HEAD3 -->|next| NODE3
NODE3 -.->|prev| HEAD3
end
AQS3 -->|head| HEAD3
AQS3 -->|tail| NODE3
end
subgraph "线程状态"
ThreadA3["线程A: 持有锁,执行临界区"]
ThreadB3["线程B: BLOCKED (LockSupport.park)"]
end
subgraph "指针说明"
PTR1["head指针 -> 队列头部(虚拟节点)"]
PTR2["tail指针 -> 队列尾部(最后入队节点)"]
PTR3["next指针 -> 正向链接"]
PTR4["prev指针 -> 反向链接"]
end
style AQS3 fill:#ffecb3
style HEAD3 fill:#e0e0e0
style NODE3 fill:#ffcdd2
style ThreadA3 fill:#4caf50,color:#fff
style ThreadB3 fill:#f44336,color:#fff
style PTR1 fill:#e3f2fd
style PTR2 fill:#e3f2fd
style PTR3 fill:#f3e5f5
style PTR4 fill:#f3e5f5
4.4 阶段3:线程A释放锁
执行流程:
- 线程A调用
lock.unlock() - 进入
AQS.release(1) Sync.tryRelease(1)执行:getState() - 1 = 0setExclusiveOwnerThread(null)setState(0)- 返回 true(锁完全释放)
signalNext(head)执行:- 找到head.next节点(ThreadB的节点)
getAndUnsetStatus(WAITING)清除WAITING状态LockSupport.unpark(ThreadB)唤醒线程B
graph LR
subgraph "AQS状态 - 线程A释放锁后"
AQS4["AQS<br/>state: 0<br/>exclusiveOwnerThread: null"]
subgraph "CLH队列"
HEAD4["Head Node<br/>status: 0<br/>waiter: null<br/>prev: null"]
NODE4["Node (ThreadB)<br/>status: 0 (已清除WAITING)<br/>waiter: ThreadB<br/>prev: head<br/>next: null"]
HEAD4 -->|next| NODE4
NODE4 -.->|prev| HEAD4
end
AQS4 -->|head| HEAD4
AQS4 -->|tail| NODE4
end
subgraph "线程状态"
ThreadA4["线程A: 释放锁,退出临界区"]
ThreadB4["线程B: 被唤醒,准备重新尝试获取锁"]
end
subgraph "唤醒过程"
SIGNAL1["1. signalNext(head)"]
SIGNAL2["2. 找到head.next节点"]
SIGNAL3["3. getAndUnsetStatus(WAITING)"]
SIGNAL4["4. LockSupport.unpark(ThreadB)"]
SIGNAL1 --> SIGNAL2
SIGNAL2 --> SIGNAL3
SIGNAL3 --> SIGNAL4
end
style AQS4 fill:#e8f5e8
style HEAD4 fill:#e0e0e0
style NODE4 fill:#fff9c4
style ThreadA4 fill:#9e9e9e
style ThreadB4 fill:#ff9800,color:#fff
style SIGNAL1 fill:#e1f5fe
style SIGNAL2 fill:#e1f5fe
style SIGNAL3 fill:#e1f5fe
style SIGNAL4 fill:#e1f5fe
4.5 阶段4:线程B被唤醒,获取锁成功
执行流程:
- 线程B从
LockSupport.park()中被唤醒 - 重新进入
acquire方法的循环 - 检查
first = (head == pred),发现自己是第一个等待者 - 调用
tryAcquire(1):getState() == 0(锁已释放)!hasQueuedPredecessors()(自己就是第一个)compareAndSetState(0, 1)成功setExclusiveOwnerThread(ThreadB)
- 更新head指针,将自己的节点设为新的head
- 清理前驱节点
graph LR
subgraph "AQS状态 - 线程B获取锁后"
AQS5["AQS<br/>state: 1<br/>exclusiveOwnerThread: ThreadB"]
subgraph "CLH队列"
HEAD5["Head Node (原ThreadB节点)<br/>status: 0<br/>waiter: null (已清除)<br/>prev: null<br/>next: null"]
end
AQS5 -->|head| HEAD5
AQS5 -->|tail| HEAD5
end
subgraph "线程状态"
ThreadA5["线程A: 已完成"]
ThreadB5["线程B: 持有锁,执行临界区"]
end
subgraph "队列变化过程"
CHANGE1["1. ThreadB获取锁成功"]
CHANGE2["2. 将ThreadB节点设为新head"]
CHANGE3["3. 清除waiter引用"]
CHANGE4["4. head和tail都指向同一节点"]
CHANGE5["5. 队列回到单节点状态"]
CHANGE1 --> CHANGE2
CHANGE2 --> CHANGE3
CHANGE3 --> CHANGE4
CHANGE4 --> CHANGE5
end
style AQS5 fill:#c8e6c9
style HEAD5 fill:#e0e0e0
style ThreadA5 fill:#e0e0e0
style ThreadB5 fill:#4caf50,color:#fff
style CHANGE1 fill:#e8f5e8
style CHANGE2 fill:#e8f5e8
style CHANGE3 fill:#e8f5e8
style CHANGE4 fill:#e8f5e8
style CHANGE5 fill:#e8f5e8
4.6 关键状态变化总结
stateDiagram-v2
[*] --> 锁空闲: 初始状态<br/>state=0, head=null
锁空闲 --> A持有锁: ThreadA.lock()<br/>state=1, owner=A
A持有锁 --> A持有锁_B等待: ThreadB.lock()<br/>创建队列, B入队阻塞
A持有锁_B等待 --> 锁空闲_B唤醒: ThreadA.unlock()<br/>state=0, 唤醒B
锁空闲_B唤醒 --> B持有锁: ThreadB获取锁<br/>state=1, owner=B
B持有锁 --> [*]: ThreadB.unlock()
note right of A持有锁_B等待
队列状态:
head -> dummy
tail -> NodeB(WAITING)
end note
note right of 锁空闲_B唤醒
关键操作:
1. signalNext(head)
2. LockSupport.unpark(B)
3. B重新进入acquire循环
end note
4.7 内存可见性保证
在整个过程中,AQS通过以下机制保证内存可见性:
- volatile字段:
state、head、tail都是volatile,保证跨线程可见 - CAS操作:具有内存屏障语义,保证操作的原子性和可见性
- LockSupport:park/unpark操作具有内存同步语义
- happens-before关系:unlock操作happens-before后续的lock操作
4.8 复杂竞争场景:多线程等待队列
为了更好地理解tail指针的重要性,我们来看一个更复杂的场景:线程A持有锁,线程B、C、D依次尝试获取锁。
graph LR
subgraph "多线程竞争场景 - 完整队列状态"
AQS6["AQS<br/>state: 1<br/>exclusiveOwnerThread: ThreadA"]
subgraph "CLH等待队列"
HEAD6["Head Node (虚拟)<br/>status: 0<br/>waiter: null<br/>prev: null<br/>next: NodeB"]
NODEB["NodeB<br/>status: WAITING(1)<br/>waiter: ThreadB<br/>prev: head<br/>next: NodeC"]
NODEC["NodeC<br/>status: WAITING(1)<br/>waiter: ThreadC<br/>prev: NodeB<br/>next: NodeD"]
NODED["NodeD (Tail)<br/>status: WAITING(1)<br/>waiter: ThreadD<br/>prev: NodeC<br/>next: null"]
HEAD6 -->|next| NODEB
NODEB -->|next| NODEC
NODEC -->|next| NODED
NODEB -.->|prev| HEAD6
NODEC -.->|prev| NODEB
NODED -.->|prev| NODEC
end
AQS6 -->|head指针| HEAD6
AQS6 -->|tail指针| NODED
end
subgraph "线程状态"
ThreadA6["线程A: 持有锁"]
ThreadB6["线程B: BLOCKED"]
ThreadC6["线程C: BLOCKED"]
ThreadD6["线程D: BLOCKED"]
end
subgraph "关键操作说明"
OP1["入队:新线程总是添加到tail后"]
OP2["出队:从head.next开始唤醒"]
OP3["遍历:hasQueuedThreads从tail向head"]
OP4["查找:getFirstQueuedThread优先head.next"]
end
style AQS6 fill:#ffecb3
style HEAD6 fill:#f5f5f5
style NODEB fill:#ffcdd2
style NODEC fill:#ffcdd2
style NODED fill:#f8bbd9
style ThreadA6 fill:#4caf50,color:#fff
style ThreadB6 fill:#f44336,color:#fff
style ThreadC6 fill:#f44336,color:#fff
style ThreadD6 fill:#f44336,color:#fff
style OP1 fill:#e1f5fe
style OP2 fill:#e1f5fe
style OP3 fill:#e1f5fe
style OP4 fill:#e1f5fe
4.9 tail指针的关键作用
- 入队操作:新线程总是通过CAS操作添加到tail指针指向的节点之后
- 队列完整性:tail指针确保我们能找到队列的最后一个节点
- 并发安全:通过原子更新tail指针,保证多线程入队的线程安全
- 遍历优化:某些操作(如hasQueuedThreads)从tail开始反向遍历更高效
5. 总结
通过对ReentrantLock从构造到lock/unlock的完整分析,我们深入理解了:
- 继承体系:ReentrantLock → Sync → AQS的层次结构
- 模板方法模式:AQS定义框架,具体同步器实现细节
- 公平性机制:通过队列检查实现FIFO语义
- 重入机制:通过state计数支持同一线程多次获取
- 内存模型:volatile + CAS实现线程安全
- 阻塞机制:LockSupport提供高效的park/unpark
- 队列管理:CLH队列变种实现高效等待
AQS作为Java并发包的基石,其设计思想和实现技巧值得深入学习和借鉴。理解AQS不仅有助于正确使用并发工具,更能提升我们设计高性能并发系统的能力。