ReentrantLock源码解析:深入剖析可重入锁的实现
本文将深入解析Java中ReentrantLock
的源码,重点分析其核心逻辑、锁的初次上锁、后续上锁以及释放过程。我们将从全局视角出发,逐步拆解代码脉络,并模拟面试场景,通过层层提问检验对ReentrantLock
的理解。文章结构清晰,先提供鸟瞰图,再细化分析,最后通过模拟面试加深理解。
一、ReentrantLock全局鸟瞰
ReentrantLock
是Java并发包(java.util.concurrent.locks
)中的一个可重入互斥锁,功能上与synchronized
类似,但提供了更高的灵活性和扩展性。它基于**AbstractQueuedSynchronizer(AQS)**实现,支持公平锁和非公平锁两种模式。
1.1 核心组件与结构
ReentrantLock
的实现依赖以下几个关键部分:
-
Sync类:
ReentrantLock
的内部抽象类,继承自AQS
,负责核心锁逻辑。- NonfairSync:非公平锁实现,允许线程插队。
- FairSync:公平锁实现,保证先到先得。
-
AQS状态(state) :使用
int
类型的state
表示锁的状态:state == 0
:锁未被占用。state > 0
:锁被占用,数值表示重入次数。
-
独占线程(exclusiveOwnerThread) :记录当前持有锁的线程。
-
核心方法:
lock()
:获取锁,可能阻塞。tryLock()
:尝试获取锁,非阻塞。unlock()
:释放锁。newCondition()
:创建条件变量,支持线程等待与唤醒。
1.2 工作原理概览
ReentrantLock
通过AQS的state
和队列机制管理锁的获取与释放:
- 初次上锁:线程尝试将
state
从0设置为1,并记录自己为锁拥有者。 - 重入上锁:同一线程再次获取锁,
state
加1。 - 释放锁:每次释放将
state
减1,当state
为0时,锁完全释放。 - 竞争处理:当锁被占用,线程进入AQS的等待队列,公平锁和非公平锁在竞争策略上有所不同。
1.3 公平锁与非公平锁
- 非公平锁:新线程可能插队,直接尝试获取锁,可能导致后到的线程优先获取。
- 公平锁:严格按照FIFO顺序,检查队列中是否有等待线程,防止插队。
二、ReentrantLock源码拆解
以下按照逻辑脉络拆解ReentrantLock
的核心源码,重点分析锁的实现细节。
2.1 类结构与构造函数
public class ReentrantLock implements Lock, java.io.Serializable {
private final Sync sync;
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
}
- Sync:
ReentrantLock
的核心逻辑委托给Sync
,通过构造函数选择公平锁或非公平锁。 - 默认非公平锁:无参构造函数创建
NonfairSync
实例。 - 公平锁选项:通过
fair
参数选择FairSync
。
2.2 Sync类与AQS
Sync
是ReentrantLock
的内部抽象类,继承自AQS
,提供锁的实现基础。
abstract static class Sync extends AbstractQueuedSynchronizer {
abstract boolean initialTryLock();
final void lock() {
if (!initialTryLock())
acquire(1);
}
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;
}
}
initialTryLock()
:由子类(FairSync
或NonfairSync
)实现,定义锁的初始尝试逻辑。lock()
:尝试获取锁,若失败则调用AQS的acquire
方法进入队列。tryRelease(int releases)
:释放锁,减少state
,当state
为0时清除锁拥有者。
2.3 NonfairSync(非公平锁)
static final class NonfairSync extends Sync {
final boolean initialTryLock() {
Thread current = Thread.currentThread();
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(current);
return true;
} else if (getExclusiveOwnerThread() == current) {
int c = getState() + 1;
if (c < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(c);
return true;
}
return false;
}
protected final boolean tryAcquire(int acquires) {
if (getState() == 0 && compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
}
-
initialTryLock:
- 若
state == 0
,通过CAS尝试将state
设为1,成功则记录当前线程为锁拥有者。 - 若锁已由当前线程持有,
state
加1,支持重入。
- 若
-
tryAcquire:仅在
state == 0
时尝试获取锁,非公平锁不检查队列。
2.4 FairSync(公平锁)
static final class FairSync extends Sync {
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) // overflow
throw new Error("Maximum lock count exceeded");
setState(c);
return true;
}
return false;
}
protected final boolean tryAcquire(int acquires) {
if (getState() == 0 && !hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
}
-
initialTryLock:
- 检查
hasQueuedThreads()
,确保没有其他线程在队列中等待。 - 若
state == 0
且队列为空,通过CAS获取锁。 - 支持重入,逻辑与非公平锁相同。
- 检查
-
tryAcquire:检查
hasQueuedPredecessors()
,保证公平性。
2.5 核心方法
-
lock() :
public void lock() { sync.lock(); }
调用
Sync
的lock
方法,先尝试initialTryLock
,失败则进入AQS队列。 -
unlock() :
public void unlock() { sync.release(1); }
调用AQS的
release
方法,触发tryRelease
释放锁。 -
tryLock() :
public boolean tryLock() { return sync.tryLock(); }
非公平尝试获取锁,不进入队列。
三、锁的生命周期分析
以下详细分析锁的初次上锁、后续上锁和释放过程,以非公平锁为例(公平锁类似,仅在竞争策略上不同)。
3.1 初次上锁
场景:锁未被任何线程持有(state == 0
,exclusiveOwnerThread == null
)。
-
调用
lock()
:线程调用reentrantLock.lock()
,触发NonfairSync.initialTryLock()
。 -
检查状态:
state == 0
,通过compareAndSetState(0, 1)
尝试将state
设为1。- CAS成功,设置
exclusiveOwnerThread
为当前线程,返回true
,锁获取成功。
-
竞争失败:
- 若CAS失败(其他线程抢先获取锁),调用
acquire(1)
,线程进入AQS等待队列。 - AQS通过
tryAcquire
再次尝试获取锁,若仍失败,线程阻塞。
- 若CAS失败(其他线程抢先获取锁),调用
关键点:
- 使用CAS确保原子性。
- 非公平锁允许新线程插队,可能导致队列中的线程继续等待。
3.2 后续上锁(重入)
场景:当前线程已持有锁(state > 0
,exclusiveOwnerThread == 当前线程
)。
-
调用
lock()
:再次调用lock()
,进入initialTryLock()
。 -
检查拥有者:
getExclusiveOwnerThread() == current
,确认是当前线程。state
加1,记录重入次数。
-
溢出检查:
- 若
state
溢出(超过Integer.MAX_VALUE
),抛出Error
。
- 若
-
返回:直接返回
true
,无需进入队列。
关键点:
- 重入只需更新
state
,效率高。 state
记录重入次数,支持嵌套锁。
3.3 释放锁
场景:线程调用unlock()
释放锁。
-
调用
unlock()
:触发sync.release(1)
,调用tryRelease(1)
。 -
检查拥有者:
- 若
exclusiveOwnerThread != 当前线程
,抛出IllegalMonitorStateException
。
- 若
-
更新状态:
state
减1。- 若
state == 0
,设置exclusiveOwnerThread = null
,锁完全释放。
-
唤醒线程:
- 若锁完全释放,AQS唤醒队列中的下一个线程尝试获取锁。
关键点:
- 仅当
state == 0
时锁才完全释放。 - 释放后,队列中的线程(或新线程)竞争锁。
四、模拟面试:层层拷打
以下模拟面试官对ReentrantLock
的提问,从基础到深入,检验理解深度。
4.1 基础问题
Q1:ReentrantLock
与synchronized
的区别是什么?
-
A:
ReentrantLock
是显式锁,提供更多功能,如:- 支持公平锁和非公平锁。
- 可中断锁(
lockInterruptibly
)。 - 条件变量(
Condition
),支持多条件等待。 - 尝试锁(
tryLock
),非阻塞获取。
-
synchronized
是隐式锁,简单但功能有限,依赖JVM内置监视器。
Q2:ReentrantLock
的公平锁和非公平锁有什么不同?
-
A:
- 非公平锁:新线程可插队,优先尝试CAS获取锁,可能导致后到先得,吞吐量较高。
- 公平锁:检查队列,优先让最早等待的线程获取锁,保证FIFO,防止饥饿,但吞吐量较低。
4.2 深入问题
Q3:ReentrantLock
如何实现可重入性?
-
A:
- 通过
state
记录锁的重入次数。 - 在
initialTryLock
中,若exclusiveOwnerThread
是当前线程,直接state++
。 - 释放时,
state--
,仅当state == 0
时清除exclusiveOwnerThread
。
- 通过
Q4:非公平锁的“插队”机制具体如何实现?
-
A:
- 在
NonfairSync.initialTryLock
中,新线程直接尝试compareAndSetState(0, 1)
,不检查队列。 - 若CAS成功,新线程抢占锁,即使队列中有等待线程。
- 公平锁则通过
hasQueuedThreads()
或hasQueuedPredecessors()
检查队列,防止插队。
- 在
4.3 刁钻问题
Q5:如果线程A持有锁,线程B和C在队列等待,A释放锁后会发生什么?
-
A:
- 非公平锁:A释放锁后,B(队列头部)尝试获取锁,但新线程D也可能通过CAS插队抢锁。
- 公平锁:A释放锁后,B(队列头部)优先获取锁,新线程D必须排队。
- AQS通过
unparkSuccessor
唤醒队列头部线程,具体结果取决于锁类型和竞争。
Q6:ReentrantLock
的state
溢出时会怎样?
-
A:
state
是int
类型,最大值为Integer.MAX_VALUE
(2^31-1)。- 重入时,若
state + 1 < 0
,表示溢出,抛出Error("Maximum lock count exceeded")
。 - 实际场景中,需避免过度重入。
4.4 场景问题
Q7:假设你在调试代码,发现IllegalMonitorStateException
,可能是什么原因?
-
A:
- 调用
unlock()
时,当前线程不是锁的持有者(exclusiveOwnerThread != 当前线程
)。 - 调用
Condition
的await
或signal
时,未持有锁。 - 解决方法:确保
lock()
和unlock()
成对调用,检查线程逻辑。
- 调用
Q8:如何使用ReentrantLock
实现一个线程安全的计数器?
- A:
class Counter {
private final ReentrantLock lock = new ReentrantLock();
private int count = 0;
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
public int getCount() {
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
}
- 使用
try-finally
确保锁释放。 - 每个操作都加锁,保证线程安全。
五、总结
ReentrantLock
是Java并发编程中的重要工具,基于AQS实现了高效、可重入的锁机制。通过分析源码,我们了解了:
- 全局结构:
Sync
、NonfairSync
和FairSync
的分工。 - 核心逻辑:
state
管理锁状态,CAS确保原子性。 - 生命周期:初次上锁依赖CAS,后续上锁更新
state
,释放时逐步减少state
。 - 公平性:非公平锁高吞吐,公平锁防饥饿。
通过模拟面试,我们进一步巩固了对ReentrantLock
的理解,涵盖了原理、实现和实际应用。