Lock底层源码(基于ReentrantLock与AQS)
一、整体架构
Java的Lock接口底层主要由ReentrantLock实现,而ReentrantLock的核心又依赖于AbstractQueuedSynchronizer(AQS)框架。整个机制围绕一个volatile int state变量和CLH变体队列实现。
核心组件关系
ReentrantLock
└── Sync (抽象内部类)
├── NonfairSync (非公平锁实现)
└── FairSync (公平锁实现)
└── AbstractQueuedSynchronizer (AQS框架)
└── Node (队列节点)
二、核心类
1. AbstractQueuedSynchronizer(AQS)抽象队列同步器
位置:java.util.concurrent.locks.AbstractQueuedSynchronizer
核心字段
// 使用volatile修饰,保证多线程间的内存可见性
// 0: 锁空闲 >0: 锁被持有(重入次数)
private volatile int state;
// 记录当前持有独占锁的线程
private transient Thread exclusiveOwnerThread;
CAS修改变量:判断并交换!!!
// this: 当前AQS对象
// STATE: state字段在内存中的偏移量(通过Unsafe获取)
// expect: 旧值
// update: 新值
protected final boolean compareAndSetState(int expect, int update) {
// 如果当前state==expect,则更新为update并返回true
// 否则返回false,不进行任何修改
return U.compareAndSetInt(this, STATE, expect, update);
}
获取/设置锁拥有者
// 设置持有锁的线程(在成功获取锁后调用)
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
// 获取当前持有锁的线程(用于可重入判断)
protected final Thread getExclusiveOwnerThread() {
return exclusiveOwnerThread;
}
模板方法 - tryAcquire
// 由子类实现具体的获取锁逻辑
// ReentrantLock.NonfairSync和FairSync会重写此方法
protected boolean tryAcquire(int arg) {
// AQS不实现,直接抛异常,强制子类重写
throw new UnsupportedOperationException();
}
模板方法 - tryRelease
// 由子类实现具体的释放锁逻辑
protected boolean tryRelease(int arg) {
// AQS不实现,直接抛异常,强制子类重写
throw new UnsupportedOperationException();
}
2. ReentrantLock.Sync - 锁的同步器
位置:java.util.concurrent.locks.ReentrantLock$Sync
abstract static class Sync extends AbstractQueuedSynchronizer {
// 抽象方法:由子类实现初次获取锁的逻辑(非公平/公平策略不同)
abstract boolean initialTryLock();
// 通用加锁入口方法
@ReservedStackAccess // 防止栈溢出
final void lock() {
// 【关键判断】:调用子类的initialTryLock尝试快速获取锁
// 如果返回true: 加锁成功,直接返回,不进入队列
// 如果返回false: 加锁失败,执行AQS的acquire方法进入排队流程
if (!initialTryLock())
acquire(1); // 参数1表示每次加锁state+1
}
}
3. ReentrantLock.NonfairSync - 非公平锁实现
位置:java.util.concurrent.locks.ReentrantLock$NonfairSync
initialTryLock() - 初次尝试获取锁(非公平)
static final class NonfairSync extends Sync {
/**
* 非公平锁的首次加锁尝试
* 特点:不管是否有线程在等待,直接CAS抢锁
*/
final boolean initialTryLock() {
// 获取当前执行线程的引用
Thread current = Thread.currentThread();
// 【核心1:CAS抢锁】
// 无论队列中是否有等待线程,都直接尝试将state从0改为1
// this: 当前NonfairSync对象
// 如果成功,说明抢到锁
if (compareAndSetState(0, 1)) { // first attempt is unguarded (无保护的首次尝试)
// 设置当前线程为锁的拥有者
setExclusiveOwnerThread(current);
// 返回true,lock()方法直接返回,不进入队列
return true;
}
// 【核心2:可重入逻辑】
// 如果CAS失败,检查是否是当前线程已持有锁(可重入)
else if (getExclusiveOwnerThread() == current) { // 可重入情况
// 获取当前state值并+1(重入次数+1)
int c = getState() + 1;
// 检查溢出:重入次数不能超过Integer.MAX_VALUE
if (c < 0) // overflow
throw new Error("Maximum lock count exceeded");
// 设置新的state值(无需CAS,因为只有持有锁的线程能修改)
setState(c);
// 返回true,重入加锁成功
return true;
}
// 【核心3:获取失败】
// 锁被其他线程持有,返回false,进入AQS排队
else {
return false;
}
}
tryAcquire() - AQS回调的获取方法
/**
* AQS在队列中会调用此方法再次尝试获取锁
* 注意:此方法不包含可重入逻辑,因为initialTryLock已处理
*/
protected final boolean tryAcquire(int acquires) {
// 双重检查:state==0并且CAS成功
// 这是为了防止在入队前锁刚好被释放,避免不必要的阻塞
if (getState() == 0 && compareAndSetState(0, acquires)) {
// 抢占成功,设置拥有者
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
// 否则失败
return false;
}
}
4. ReentrantLock.FairSync - 公平锁实现
位置:java.util.concurrent.locks.ReentrantLock$FairSync
initialTryLock() - 初次尝试获取锁(公平)
static final class FairSync extends Sync {
/**
* 公平锁的首次加锁尝试
* 特点:检查等待队列,如果有线程在等待,自己就不抢锁
*/
final boolean initialTryLock() {
// 获取当前线程
Thread current = Thread.currentThread();
// 获取当前state值
int c = getState();
// 【核心区别1:检查队列】
if (c == 0) {
// 【关键】:先判断是否有线程在队列中等待
// !hasQueuedThreads():如果队列中没有等待线程,才尝试CAS
// 这是保证公平性的核心:先到先得
if (!hasQueuedThreads() && compareAndSetState(0, 1)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 【核心2:可重入逻辑】(与非公平锁相同)
else if (getExclusiveOwnerThread() == current) {
if (++c < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(c);
return true;
}
// 失败:要么锁被占用,要么队列中有等待线程
return false;
}
hasQueuedThreads() - 检查等待队列
/**
* 判断同步队列中是否有等待线程
* 这是公平锁的核心判断
*/
public final boolean hasQueuedThreads() {
// 从尾节点向前遍历到head
// 循环条件:p != h && p != null 表示队列中有多个节点
for (Node p = tail, h = head; p != h && p != null; p = p.prev) {
// 检查节点状态
// status >= 0 表示线程仍在等待
if (p.status >= 0)
return true; // 发现有等待线程,返回true
}
return false; // 队列为空或只有head节点
}
}
5. ReentrantLock - 对外的锁接口
位置:java.util.concurrent.locks.ReentrantLock
加锁入口
public class ReentrantLock implements Lock {
// 同步器实例(可以是NonfairSync或FairSync)
private final Sync sync;
/**
* 加锁操作
* 直接调用sync.lock(),策略由子类决定
*/
public void lock() {
// 调用Sync子类(NonfairSync或FairSync)的lock方法
sync.lock(); // 在ReentrantLock.java 第225行附近
}
解锁入口
/**
* 解锁操作
* 调用AQS的release方法
*/
public void unlock() {
// 参数1表示每次解锁state-1
sync.release(1);
}
}
三、加锁流程详细步骤
非公平锁加锁全流程(NonfairSync)
场景:线程A第一次获取锁,线程B尝试获取已被A持有的锁
// 步骤1:调用ReentrantLock.lock()
public void lock() {
sync.lock(); // 跳转到NonfairSync.lock()
}
// 步骤2:进入Sync.lock()
final void lock() {
if (!initialTryLock()) // 先尝试快速获取
acquire(1); // 失败则进入AQS排队
}
// 步骤3:执行NonfairSync.initialTryLock()
final boolean initialTryLock() {
Thread current = Thread.currentThread();
// 【线程A】:state=0, CAS成功
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(current); // A成为锁拥有者
return true; // A加锁成功,直接返回
}
// 【线程B】:state=1(CAS失败) 且 拥有者是A(不是B)
else {
return false; // B获取失败,进入acquire(1)
}
}
// 步骤4:线程B进入AQS.acquire()
public final void acquire(int arg) {
// 再次尝试tryAcquire()(防止在入队前锁刚好释放)
if (!tryAcquire(arg))
acquire(null, arg, false, false, false, 0L); // 加入等待队列
}
// 步骤5:执行NonfairSync.tryAcquire()
protected final boolean tryAcquire(int acquires) {
// B检查时state=1(还是A持有),CAS失败
// 返回false,进入acquire(...)入队阻塞
if (getState() == 0 && compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
// 步骤6:线程B进入CLH队列,调用LockSupport.park()阻塞
// 步骤7:线程A执行unlock(),state变为0,调用LockSupport.unpark(B)
// 步骤8:线程B被唤醒,再次自旋调用tryAcquire(),此时state=0,CAS成功,获取锁
公平锁加锁全流程(FairSync)
与非公平锁的核心区别在第3步:
// 步骤3:执行FairSync.initialTryLock()
final boolean initialTryLock() {
Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 【核心区别】:检查hasQueuedThreads()
// 即使state=0,如果有线程在等待,也不抢锁
if (!hasQueuedThreads() && compareAndSetState(0, 1)) {
setExclusiveOwnerThread(current);
return true;
}
}
// ...可重入逻辑相同
return false;
}
// 步骤3.1:hasQueuedThreads()检查
public final boolean hasQueuedThreads() {
// 如果tail!=head且tail不为null,说明队列中有等待节点
for (Node p = tail, h = head; p != h && p != null; p = p.prev) {
if (p.status >= 0) // 发现有效等待节点
return true; // 返回true,放弃抢锁机会
}
return false; // 队列为空,可以公平竞争
}
四、解锁流程详细步骤
解锁全流程(以线程A释放锁为例)
// 步骤1:调用ReentrantLock.unlock()
public void unlock() {
sync.release(1); // 参数1表示releases=1
}
// 步骤2:进入AQS.release()
public final boolean release(int arg) {
// 尝试释放锁
if (tryRelease(arg)) {
// 释放成功,唤醒后继节点
signalNext(head);
return true;
}
return false; // 重入锁未完全释放
}
// 步骤3:执行Sync.tryRelease()(ReentrantLock重写的方法)
@ReservedStackAccess
protected final boolean tryRelease(int releases) {
// 获取当前state并减去releases(releases=1)
int c = getState() - releases; // c = 当前重入次数 - 1
// 【安全性检查】:只有持有锁的线程才能解锁
if (getExclusiveOwnerThread() != Thread.currentThread())
throw new IllegalMonitorStateException(); // 非法状态异常
boolean free = (c == 0); // 判断锁是否完全释放
// 如果state变为0,说明锁已完全释放
if (free)
setExclusiveOwnerThread(null); // 清空锁拥有者
// 更新state值(无需CAS,因为当前线程持有锁)
setState(c); // state减1
return free; // 返回true表示完全释放,需要唤醒等待线程
}
// 步骤4:执行signalNext(head)唤醒后继节点
// 找到head.next对应的线程,调用LockSupport.unpark(thread)
五、关键机制深度解析
1. 可重入性(Reentrancy)实现
// 原理:记录锁持有线程 + state计数器
// 第一次获取锁:state=1, exclusiveOwnerThread=当前线程
// 同一线程再次获取:state=2,3,4...(不重设exclusiveOwnerThread)
// 解锁:state递减,直到state=0才完全释放
2. volatile与CAS无锁化
// volatile:保证所有线程看到的state值是最新的
// CAS操作:compareAndSetState()利用CPU指令实现原子性,无需synchronized
// 性能:在高并发下比重量级锁高效
3. 公平性(Fairness)机制
// 非公平锁:线程直接CAS抢锁,不管队列
// 优点:吞吐量高,线程切换少
// 缺点:可能导致线程饥饿(可以使用公平锁,也就是添加个true)
// 公平锁:先检查hasQueuedThreads(),有等待者就排队
// 优点:保证先到先服务,避免饥饿
// 缺点:吞吐量略低,所有线程必须排队
4. AQS队列机制(CLH变体)
abstract static class Node {
volatile Node prev; // 前驱节点
volatile Node next; // 后继节点
Thread waiter; // 等待的线程
volatile int status; // 节点状态:0(初始)
}
// head: 持有锁的节点或虚拟节点
// tail: 队列尾部,新节点从这里加入
// 入队:CAS设置tail,然后prev指向旧tail
// 出队:head向后移动,唤醒next节点
六、总结对比表
| 特性 | NonfairSync (非公平锁) | FairSync (公平锁) |
|---|---|---|
| 初次尝试 | 直接CAS抢锁 | 检查队列,无等待者才CAS |
| 吞吐量 | 高 | 略低 |
| 线程饥饿 | 可能 | 不会 |
| state操作 | CAS + 直接set | CAS + 直接set |
| 适用场景 | 高并发,竞争激烈 | 要求公平性,避免饥饿 |
七、关键源码行号速查
(行号不一定准,还是自己实操一次最好!)
- ReentrantLock.lock(): 225行
- ReentrantLock.unlock(): 173行
- NonfairSync.initialTryLock(): 229行(state+1)
- FairSync.initialTryLock(): 263行(state+1)
- Sync.tryRelease(): 173行(state-1)
八、核心acquire方法详解
源码详解
public final void acquire(int arg) {
// 这是AQS的入口方法,先快速尝试一次tryAcquire,失败才进入复杂的acquire排队逻辑
// arg=1表示获取1个资源(对于ReentrantLock就是锁的重入次数+1)
if (!tryAcquire(arg))
// acquire是更底层的实现,会处理入队、自旋、阻塞等完整生命周期
// 参数含义:node=null(还未创建节点), arg=1, shared=false(独占模式), interruptible=false(不可中断), timed=false(不超时), time=0L
acquire(null, arg, false, false, false, 0L);
}
/**
* AQS最核心、最复杂的获取锁方法,融合了自旋、排队、阻塞、超时、中断等全部逻辑
* 这是现代JVM并发编程的巅峰之作,性能优化的集大成者
*
* @param node 当前线程对应的队列节点(可能为null,表示还没创建)
* @param arg 获取锁的资源数(ReentrantLock传1)
* @param shared 是否为共享模式(读写锁读锁为true,ReentrantLock为false)
* @param interruptible 是否可被中断唤醒(true表示lockInterruptibly)
* @param timed 是否有超时时间(true表示tryLock带超时)
* @param time 超时截止时间(纳秒)
* @return 1表示成功获取锁,负值表示失败(被中断或超时)
*/
final int acquire(Node node, int arg, boolean shared,
boolean interruptible, boolean timed, long time) {
// 获取当前线程引用,后续所有操作都针对这个线程
Thread current = Thread.currentThread();
// spins: 当前自旋次数(队首线程在park前的自旋)
// postSpins: 被唤醒后的再次自旋次数(指数退避策略)
// 使用byte类型是为了节省内存空间
byte spins = 0, postSpins = 0;
// interrupted: 是否被中断过的标记
// first: 当前节点是否为队列第一个等待节点(动态变化)
boolean interrupted = false, first = false;
// pred: 前驱节点缓存,避免重复读取(性能优化)
Node pred = null;
/*
* 这是整个方法的核心循环,线程会在这个循环中经历以下状态转换:
* 未入队 -> 尝试获取 -> 创建节点 -> 入队 -> 自旋 -> 阻塞 -> 被唤醒 -> 再次尝试
*
* 设计理念:延迟一切(lazy evaluation)+ 乐观尝试(optimistic retry)
* 不到万不得已不阻塞,阻塞前也要自旋几次,平衡CPU消耗和响应延迟
*/
for (;;) { // 无限循环,直到获取锁或取消
// ------------------- 阶段1:判断当前节点是否为队列第一个等待节点 -------------------
// 这个判断极其关键,只有队首节点才有资格尝试获取锁(公平性保证)
if (!first && (pred = (node == null) ? null : node.prev) != null &&
!(first = (head == pred))) {
// 进入这里说明:1. 不是队首 2. 节点已入队 3. 前驱不是head
// 情况1:前驱节点状态为CANCELLED(>0表示取消),需要清理无效节点
// 否则会导致队列死链,永远无法唤醒
if (pred.status < 0) {
cleanQueue(); // 清理前驱已取消的节点,重建队列链表
continue; // 清理后重新循环
}
// 情况2:前驱的prev为null,说明前驱正在入队中,链接未完成
// 此时需要自旋等待,避免过早park错过唤醒信号
else if (pred.prev == null) {
Thread.onSpinWait(); // 调用CPU的PAUSE指令,降低功耗,提高自旋效率
continue; // 继续循环等待前驱入队完成
}
}
// ------------------- 阶段2:尝试获取锁(黄金时机) -------------------
// 只有两种情况可以在此尝试获取锁:
// 1. first=true: 已经是队首节点,轮到我获取锁了
// 2. pred=null: 还没入队,新来的直接尝试(非公平性的体现)
if (first || pred == null) {
boolean acquired;
try {
// 调用子类实现的tryAcquire或tryAcquireShared
// ReentrantLock会调用NonfairSync/FairSync的tryAcquire进行CAS抢锁
if (shared)
acquired = (tryAcquireShared(arg) >= 0);
else
acquired = tryAcquire(arg);
} catch (Throwable ex) {
// 获取锁过程中抛出异常(罕见),取消当前节点并重新抛出
cancelAcquire(node, interrupted, false);
throw ex;
}
// 成功获取锁!需要进行一系列状态更新和清理工作
if (acquired) {
if (first) {
// 当前节点晋升为新的head(旧head已释放锁)
node.prev = null; // 断开与前驱的链接
head = node; // 设置新head
pred.next = null; // 帮助GC回收旧head
node.waiter = null; // 清空线程引用,节点已获取锁不再需要waiter
// 共享模式下需要传播唤醒(如读写锁读锁)
if (shared)
signalNextIfShared(node);
// 如果被中断过且需要响应中断,恢复中断标记
if (interrupted)
current.interrupt();
}
return 1; // **成功获取锁,返回正值1,方法结束**
}
// 获取失败,继续循环(锁还没释放)
}
// ------------------- 阶段3:创建节点并入队(第一次循环时node为null) -------------------
// 情况1:节点还未创建,延迟创建以避免不必要的对象分配(性能优化)
if (node == null) {
if (shared)
node = new SharedNode(); // 创建共享模式节点
else
node = new ExclusiveNode(); // 创建独占模式节点(ReentrantLock走这里)
}
// 情况2:节点已创建,但还没有入队(prev为null)
else if (pred == null) {
node.waiter = current; // 将当前线程绑定到节点
Node t = tail; // 获取当前tail(队尾)
node.setPrevRelaxed(t); // 先用relaxed模式设置前驱,避免内存屏障开销
// 如果tail为null,说明队列未初始化,先创建虚拟head节点
if (t == null)
tryInitializeHead(); // 初始化head和tail
// CAS竞争失败:有其他线程同时入队,回滚prev,下次循环重试
else if (!casTail(t, node))
node.setPrevRelaxed(null); // 失败必须回滚,否则会成为野指针
// CAS成功:成为新的tail,完成双向链表链接
else
t.next = node; // 旧tail的next指向新节点,完成入队
}
// ------------------- 阶段4:自适应自旋(Park前的最后挣扎) -------------------
// 已经成为队首,但锁还没释放,先自旋几次再park
// 这样可以减少线程上下文切换的开销,提高吞吐量
else if (first && spins != 0) {
--spins; // 自旋次数递减
Thread.onSpinWait(); // CPU友好的自旋等待(PAUSE指令)
}
// ------------------- 阶段5:准备阻塞(设置状态并Park) -------------------
// 情况1:节点状态为0(初始状态),先设置为WAITING(可被唤醒状态)
// 这是两步阻塞法:先标记状态,下次循环才真正park
// 避免在设置状态前被唤醒导致信号丢失(race condition)
else if (node.status == 0) {
node.status = WAITING; // 设置状态为等待,使能被唤醒
}
// 情况2:状态已设置,真正阻塞当前线程
else {
// 指数退避策略:每次被唤醒后,postSpins自旋次数翻倍
// 应对虚假唤醒,避免频繁park/unpark
spins = postSpins = (byte)((postSpins << 1) | 1);
long nanos;
// 根据是否超时选择不同的park方式
if (!timed)
LockSupport.park(this); // 无限期阻塞,等待unpark
else if ((nanos = time - System.nanoTime()) > 0L)
LockSupport.parkNanos(this, nanos); // 超时阻塞
else
break; // 超时时间已到,退出循环
// 被唤醒后清除WAITING状态,准备下次循环重新尝试
node.clearStatus();
// 检查是否被中断,如果是可中断模式则退出循环
// Thread.interrupted()会清除中断标记,所以用|=记录历史上是否中断过
if ((interrupted |= Thread.interrupted()) && interruptible)
break; // 被中断且允许中断,退出循环
}
}
// ------------------- 阶段6:取消获取(超时或被中断) -------------------
// 只有超时或被中断才会走到这里
// cancelAcquire会清理节点,还原状态,返回负值表示失败
return cancelAcquire(node, interrupted, interruptible);
}
完整流程图解(白话版)
线程来了 → 先抢锁(tryAcquire)→ 抢不到 → 进acquire方法
↓
循环开始:我是队首吗?→ 不是 → 前驱正常吗?→ 不正常 → 清理队列
↓
是队首/还没入队 → 再抢一次锁 → 抢到 → 晋升head → 返回成功
↓
没抢到 → 我创建节点了吗?→ 没 → 创建节点
↓
节点有了 → 我入队了吗?→ 没 → CAS入队 → 失败就重试
↓
入队了 → 我是队首吗?→ 是 → 自旋几次(别急着睡)
↓
自旋够了 → 设置WAITING标记 → 调用park()阻塞(真正睡觉)
↓
被唤醒 → 清除标记 → 检查中断/超时 → 回到循环开始再抢锁
↓
超时/中断 → cancelAcquire → 返回失败
核心设计
- 延迟一切(Lazy Evaluation):节点延迟创建、阻塞延迟执行、清理延迟进行,避免不必要的开销
- 乐观尝试(Optimistic Retry):不到最后一刻不放弃,park前多次tryAcquire,减少线程切换
- 两步阻塞(Two-Phase Park):先标记状态再真正park,避免信号丢失(经典race condition解决方案)
- 指数退避(Exponential Backoff):被唤醒后自旋次数指数增长,应对虚假唤醒
- 自适应(Adaptive):根据first/pred动态调整策略,不是队首就安心等待,是队首就积极尝试
- 无锁化(Lock-Free):队列操作全程CAS,只有park是内核调用,最大化并发性能

170万+

被折叠的 条评论
为什么被折叠?



