Java中ReentrantLock 深度剖析
ReentrantLock 深度剖析(基于Java 21)
一、底层实现原理
整体架构
ReentrantLock 是 JUC 中最核心的显式锁,完全基于 AQS(AbstractQueuedSynchronizer) 构建。其类结构如下:
ReentrantLock
├── implements Lock
└── 内部持有 Sync extends AQS
├── NonfairSync extends Sync (默认,非公平)
└── FairSync extends Sync (公平模式)
核心算法:AQS state + CLH 变体队列
┌──────────────────────────────────────────────────────────┐
│ AQS.state 语义(ReentrantLock 的定义) │
│ │
│ state == 0 → 锁空闲,无线程持有 │
│ state == 1 → 锁被某线程持有(第一次获取) │
│ state == N → 同一线程重入了 N 次(可重入计数) │
│ │
│ 持有者:exclusiveOwnerThread(AQS 父类字段) │
└──────────────────────────────────────────────────────────┘
公平 vs 非公平的核心差异
非公平锁(NonfairSync):
lock() → 先 CAS 尝试抢占(插队)→ 失败才入队等待
优点:吞吐量高(减少线程切换)
缺点:可能导致队列中线程长期饥饿
公平锁(FairSync):
lock() → 检查队列是否有等待者 → 有则直接入队,不插队
优点:FIFO 顺序,无饥饿
缺点:线程上下文切换频繁,吞吐量低 20%~30%
关键字段
// AQS 中的核心字段(ReentrantLock 通过继承使用)
// 同步状态:0=无锁,N=重入次数
private volatile int state;
// 当前持锁线程(AbstractOwnableSynchronizer 中定义)
private transient Thread exclusiveOwnerThread;
// CLH 变体等待队列的头节点(dummy 哑节点)
private transient volatile Node head;
// CLH 变体等待队列的尾节点
private transient volatile Node tail;
二、核心数据结构
2.1 AQS 的 CLH 变体等待队列
AQS 使用的不是原始 CLH 队列,而是其变体,区别在于:原始 CLH 是自旋前驱节点的状态,AQS 变体改为挂起线程(park)+ 由前驱唤醒(unpark),避免忙等。
Java 21 的重大变化:队列节点从 Node 改为 AbstractQueuedSynchronizer.Node(内部类重构),但逻辑与 Java 8 一脉相承。
CLH 变体队列结构(双向链表):
prev prev prev
head ←──── Node-A(Thread1) ←──── Node-B(Thread2) ←──── tail
────► ────► ────►
next next next
head:dummy 哑节点(不代表实际线程,初始化时创建)
每个 Node 持有:
- thread → 被挂起的线程引用
- waitStatus → 节点状态(见下表)
- prev/next → 双向链接
- nextWaiter → Condition 队列链接(独占模式为 null)
waitStatus 状态机:
| 值 | 常量 | 含义 |
|---|---|---|
| 0 | 初始值 | 节点刚入队,尚未设置状态 |
| -1 | SIGNAL |
后继节点需要被唤醒(最常见) |
| -2 | CONDITION |
节点在 Condition 队列中等待 |
| -3 | PROPAGATE |
共享模式下传播唤醒(ReentrantLock 不用) |
| 1 | CANCELLED |
线程已超时/中断,节点废弃 |
2.2 Condition 队列(单向链表)
每个 Condition 对象内部维护一条独立的单向等待队列,与 AQS 主队列分离:
ConditionObject 内部:
firstWaiter → Node(T1) → Node(T2) → Node(T3) → null
↑
lastWaiter
await(): 当前节点从 AQS 主队列移到 Condition 队列
signal(): 将 Condition 队列头节点转移回 AQS 主队列
2.3 Java 21 中的节点结构演进
Java 21 对 AQS 内部做了结构性重构(JEP 相关优化),引入了 ExclusiveNode 和 SharedNode 的区分,但 ReentrantLock 仅用独占模式,核心逻辑不变:
// Java 21 AQS Node(简化)
abstract static class Node {
volatile Node prev;
volatile Node next;
Thread waiter; // Java 21 改名:原 thread 字段
volatile int status; // Java 21 改名:原 waitStatus 字段
}
static final class ExclusiveNode extends Node { } // 独占模式节点
static final class SharedNode extends Node { } // 共享模式节点
static final class ConditionNode extends Node // Condition 节点
implements ForkJoinPool.ManagedBlocker { ... }
三、源码关键路径
3.1 lock() 路径 —— 非公平锁
// NonfairSync.lock()
final void lock() {
// ① 第一步:直接 CAS 尝试抢锁(不管队列有无等待者,插队!)
if (!initialTryLock())
acquire(1); // CAS 失败 → 走完整 AQS 获取流程
}
// NonfairSync.initialTryLock()
final boolean initialTryLock() {
Thread current = Thread.currentThread();
// CAS:将 state 从 0 改为 1
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(current); // 记录持锁线程
return true;
}
// CAS 失败,检查是否是重入(当前线程已持锁)
else if (getExclusiveOwnerThread() == current) {
int c = getState() + 1;
if (c < 0) throw new Error("Maximum lock count exceeded"); // 溢出保护
setState(c); // 重入:state + 1(无需 CAS,持锁线程独占)
return true;
}
return false; // 真正的竞争失败
}
// AQS.acquire(1) —— 完整获取流程
public final void acquire(int arg) {
// tryAcquire:再试一次(非公平下仍可能插队成功)
if (!tryAcquire(arg))
// 失败 → 入队 → 挂起等待
acquire(null, arg, false, false, false, 0L);
}
// AQS.acquire()(Java 21 重构后的完整版本,含超时/中断支持)
final int acquire(Node node, int arg, boolean shared,
boolean interruptible, boolean timed, long time) {
Thread current = Thread.currentThread();
byte spins = 0, postSpins = 0;
boolean interrupted = false, first = false;
Node pred = null;
for (;;) {
// 检查当前节点是否是队列中第一个真实等待者(head 的后继)
if (!first && (pred = (node == null) ? null : node.prev) != null
&& !(first = (head == pred))) {
if (pred.status < 0) { // 前驱已取消
cleanQueue(); // 清理取消节点
continue;
} else if (pred.prev == null) {
Thread.onSpinWait(); // 前驱是 head,自旋等待
continue;
}
}
if (first || pred == null) {
boolean acquired;
try {
// 尝试获取锁(非公平再次插队,公平检查队列)
acquired = tryAcquire(arg);
} catch (Throwable ex) {
cancelAcquire(node, interrupted, false);
throw ex;
}
if (acquired) {
// 成功获取锁:将自己设为新 head
if (first) {
node.prev = null;
head = node;
pred.next = null;
node.waiter = null;
if (shared) signalNextIfShared(node);
if (interrupted) current.interrupt();
}
return 1; // 获取成功
}
}
// 自旋短暂次数后 park 挂起线程
Node t;
if ((t = tail) == null) { // 队列未初始化
tryInitializeHead();
} else if (node == null) { // 创建节点并入队
node = new ExclusiveNode();
} else if (pred == null) { // 将节点加到队尾(CAS)
node.waiter = current;
Node p = tail;
node.setPrevRelaxed(p);
if (casTail(p, node)) pred = p;
else node.setPrevRelaxed(null);
} else if (first && spins != 0) { // 短暂自旋
--spins;
Thread.onSpinWait();
} else if (node.status == 0) { // 设置状态为等待被唤醒
node.status = WAITING;
} else {
// park 挂起,等待前驱 unpark 唤醒
long nanos;
spins = postSpins = (byte)((postSpins << 1) | 1);
if (!timed) LockSupport.park(this);
else if ((nanos = time - System.nanoTime()) > 0L)
LockSupport.parkNanos(this, nanos);
else break;
// 唤醒后检查中断
node.clearStatus();
if ((interrupted |= Thread.interrupted()) && interruptible) break;
}
}
return cancelAcquire(node, interrupted, interruptible);
}
3.2 unlock() 路径
// ReentrantLock.unlock()
public void unlock() {
sync.release(1);
}
// AQS.release(1)
public final boolean release(int arg) {
if (tryRelease(arg)) { // 尝试释放
signalNext(head); // 唤醒队列中下一个等待者
return true;
}
return false;
}
// Sync.tryRelease()
protected final boolean tryRelease(int releases) {
int c = getState() - releases; // 重入计数减一
if (getExclusiveOwnerThread() != Thread.currentThread())
throw new IllegalMonitorStateException(); // 非持锁线程不能释放
boolean free = (c == 0); // state 降为 0 才是真正释放
if (free)
setExclusiveOwnerThread(null); // 清除持锁线程
setState(c); // 写 volatile state(触发内存屏障!)
return free;
}
// AQS.signalNext()(Java 21)
private static void signalNext(Node h) {
Node s;
if (h != null && (s = h.next) != null && s.status != 0) {
s.getAndUnsetStatus(WAITING); // CAS 清除 WAITING 状态
LockSupport.unpark(s.waiter); // 精确唤醒后继线程
}
}
3.3 Condition.await() / signal() 路径
// ConditionObject.await()
public final void await() throws InterruptedException {
if (Thread.interrupted()) throw new InterruptedException();
// ① 将当前线程节点加入 Condition 单向队列
ConditionNode node = newConditionNode();
// ② 完全释放锁(state 归零,支持重入),保存重入次数
int savedState = enableWait(node);
// ③ park 等待 signal
LockSupport.setCurrentBlocker(this);
boolean interrupted = false, cancelled = false, rejected = false;
while (!canReacquire(node)) { // 未转移回 AQS 主队列则继续等待
if (interrupted |= Thread.interrupted()) {
if (cancelled = (node.getAndUnsetStatus(COND) != 0))
break; // 中断处理
} else if (node.status != 0) {
LockSupport.parkNanos(this, 0L);
} else
LockSupport.park(this); // 挂起
}
LockSupport.setCurrentBlocker(null);
// ④ 重新获取锁(恢复原重入次数)
node.clearStatus();
acquire(node, savedState, false, false, false, 0L);
if (interrupted) { ... } // 处理中断
}
// ConditionObject.signal()
public final void signal() {
ConditionNode first = firstWaiter;
if (!isHeldExclusively()) throw new IllegalMonitorStateException();
if (first != null)
doSignal(first, false); // 将 Condition 队列头节点转移到 AQS 主队列
}
// doSignal:将节点从 Condition 队列转移到 AQS 等待队列
private void doSignal(ConditionNode first, boolean all) {
while (first != null) {
ConditionNode next = first.nextWaiter;
if ((firstWaiter = next) == null) lastWaiter = null;
// CAS 将节点状态从 COND 改为 0,成功则加入 AQS 队列
if ((first.getAndUnsetStatus(COND) & COND) != 0) {
enqueue(first); // 加入 AQS 主队列尾部
if (!all) break;
}
first = next;
}
}
3.4 完整流程图
lock() 调用链:
ReentrantLock.lock()
│
▼
NonfairSync.lock()
│
├─ initialTryLock() ──CAS成功──► 持锁成功,返回
│ │
│ 失败
│ ▼
└─ AQS.acquire(1)
│
├─ tryAcquire(1) ──成功──► 持锁成功,返回
│ │
│ 失败
│ ▼
├─ new ExclusiveNode() 创建节点
├─ casTail() 入队
├─ 短暂自旋(onSpinWait)
├─ node.status = WAITING
└─ LockSupport.park() 挂起
│
被 unpark 唤醒
│
▼
tryAcquire() 重新竞争
成功 → 将自身设为新 head,返回
unlock() 调用链:
ReentrantLock.unlock()
│
▼
AQS.release(1)
│
├─ Sync.tryRelease(1)
│ ├─ state - 1
│ ├─ state==0 → 清除 ownerThread
│ └─ setState(volatile写) ←── 内存屏障!
│
└─ signalNext(head)
└─ LockSupport.unpark(后继线程)
四、AQS 关联分析
ReentrantLock 与 AQS 的关系
ReentrantLock 是 AQS 的典型独占模式使用者,关系如下:
ReentrantLock
└── Sync(内部类)extends AbstractQueuedSynchronizer
├── 实现 tryAcquire(int) → 定义"如何尝试获取锁"
├── 实现 tryRelease(int) → 定义"如何释放锁"
└── 使用 AQS 提供的:
state 字段 → 锁的重入计数
CLH 变体队列 → 等待线程排队
park/unpark 框架 → 线程挂起/唤醒
AQS 模板方法模式
// AQS 是模板方法框架,定义好"骨架",子类只需实现"钩子":
// ── AQS 提供的骨架(不可覆盖)─────────────────────────────────
acquire() → 完整获取流程(入队、挂起、唤醒后重试)
release() → 完整释放流程(tryRelease + 唤醒后继)
acquireInterruptibly() → 可中断版本
tryAcquireNanos() → 带超时版本
// ── 子类必须实现的钩子 ──────────────────────────────────────
tryAcquire(int) → ReentrantLock 实现:CAS state,记录 ownerThread
tryRelease(int) → ReentrantLock 实现:state-1,state==0 则清除 owner
state 字段的完整状态机
CAS(0→1) 成功
┌──── 或重入 setState(n+1)
│
│ state=0 state=1 state=N(N>1)
│ (无锁)─────────►(持锁)────────►(重入 N 次)
│ │ ▲ │ ▲
│ │ │lock()重入 │ │lock()再重入
└──────────────────────┘ └───────────────┘
setState(0)
(unlock,state-1 降为 0)
CLH 队列如何保证公平性
公平锁 FairSync.tryAcquire() 中的关键判断:
if (!hasQueuedPredecessors()) {
// 队列为空 或 当前线程就是队列头的后继 → 才允许 CAS 抢锁
if (compareAndSetState(0, 1)) { ... }
}
hasQueuedPredecessors() 返回 true 的含义:
队列中有其他线程在当前线程之前等待
→ 当前线程不允许插队,必须排队
五、内存模型 / happens-before
核心保证链路
线程 A(持锁,释放) 线程 B(等待,获取)
临界区内的写操作
│
▼
unlock() → tryRelease()
│
▼
setState(0) ← volatile 写(写屏障 StoreLoad)
│
────────────────── happens-before ──────────────────
│
▼
tryAcquire() 中
compareAndSetState(0,1)
← volatile 读(读屏障 LoadLoad)
│
▼
临界区内的读操作
一定能看到 A 的写
happens-before 规则对应
| 规则 | 在 ReentrantLock 中的体现 |
|---|---|
| volatile 写 → volatile 读 | setState(0)(写)happens-before getState()(读) |
| 监视器解锁 → 后续加锁 | AQS 通过 volatile state 模拟了等价的效果 |
| unpark → park 返回 | unpark(t) happens-before 被唤醒线程的 park() 返回 |
| CAS 成功 → 后续读 | compareAndSetState 具有 volatile 读写的联合语义 |
volatile state 的内存屏障效果
// tryRelease 中:
setState(c); // volatile 写
// 效果:
// StoreStore 屏障:前面所有普通写 → 这次 volatile 写(临界区写不重排)
// StoreLoad 屏障:这次 volatile 写 → 后续任意读(最重的屏障)
// tryAcquire 中:
getState(); // volatile 读
// 效果:
// LoadLoad 屏障:这次 volatile 读 → 后续普通读(读不重排)
// LoadStore 屏障:这次 volatile 读 → 后续普通写
六、适用场景
场景一:需要可中断锁的业务
// synchronized 不响应中断,lockInterruptibly() 可以
// 典型:防止死锁、响应超时取消
lock.lockInterruptibly(); // 若被中断则抛 InterruptedException
try { ... } finally { lock.unlock(); }
适用:RPC 调用超时、用户请求取消、批量任务可取消框架
场景二:需要超时获取锁
// 避免无限等待导致线程堆积
if (lock.tryLock(200, TimeUnit.MILLISECONDS)) {
try { ... } finally { lock.unlock(); }
} else {
// 超时处理:快速失败、降级、返回错误
return Result.timeout();
}
适用:高并发接口限流、数据库连接池获取
场景三:多个 Condition 精确唤醒
// synchronized 的 wait/notifyAll 只有一个等待队列
// ReentrantLock 可创建多个 Condition,实现精确唤醒
Lock lock = new ReentrantLock();
Condition notFull = lock.newCondition(); // 生产者等待队列
Condition notEmpty = lock.newCondition(); // 消费者等待队列
// 生产者
lock.lock(); try {
while (isFull()) notFull.await();
enqueue(item);
notEmpty.signal(); // 只唤醒消费者,不打扰其他生产者
} finally { lock.unlock(); }
适用:有界阻塞队列、流量整形缓冲区(
LinkedBlockingQueue内部即如此实现)
场景四:公平锁防饥饿
// 任务调度系统:保证所有线程平等获得执行机会
ReentrantLock fairLock = new ReentrantLock(true); // 公平模式
适用:公平调度器、资源分配算法、避免某些线程长期得不到执行
场景五:tryLock 无阻塞尝试
// 多资源按顺序 tryLock,失败则全部释放,避免死锁
if (lockA.tryLock()) {
try {
if (lockB.tryLock()) {
try { doWork(); } finally { lockB.unlock(); }
}
} finally { lockA.unlock(); }
}
适用:转账等需要同时持有多个锁的场景
七、与同类方案对比
| 维度 | ReentrantLock |
synchronized |
StampedLock |
ReentrantReadWriteLock |
Semaphore |
|---|---|---|---|---|---|
| 可重入 | ✅ | ✅ | ❌(非重入) | ✅ | ❌ |
| 公平模式 | ✅ 可选 | ❌ | ❌ | ✅ 可选 | ✅ 可选 |
| 可中断 | ✅ | ❌ | ✅ | ✅ | ✅ |
| 超时获取 | ✅ | ❌ | ✅ | ✅ | ✅ |
| 多 Condition | ✅ 任意多个 | ❌(只有1个) | ❌ | ✅ 2个 | ❌ |
| 读写分离 | ❌ | ❌ | ✅ 3种模式 | ✅ 读写锁 | ❌ |
| 乐观读 | ❌ | ❌ | ✅ 无锁读 | ❌ | ❌ |
| 性能(无竞争) | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐(JVM优化) | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| 性能(高竞争) | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐(读多) | ⭐⭐⭐⭐ |
| 虚拟线程友好 | ⚠️(pin问题) | ⚠️(pin问题) | ✅(CAS主路径) | ⚠️ | ✅ |
| 死锁风险 | 需手动 unlock | 自动释放 | 需手动 unlock | 需手动 unlock | 需手动 release |
| 适用场景 | 通用精确控制 | 简单互斥 | 读多写少极致性能 | 读多写少 | 资源池限流 |
synchronized vs ReentrantLock 深度对比
synchronized 的优势(Java 21 下):
① JVM 自动释放锁(即使异常),不会忘记 unlock
② 偏向锁/轻量级锁优化(无竞争下几乎零开销)
③ JIT 编译器能对 synchronized 做锁消除、锁粗化优化
④ 代码更简洁,不易出错
ReentrantLock 的优势:
① lockInterruptibly():响应中断
② tryLock(timeout):超时获取
③ 多 Condition:精确唤醒
④ 公平锁:防止饥饿
⑤ lock.getQueueLength():可监控等待队列
⑥ lock.isLocked() 等诊断方法
结论:优先 synchronized,当需要上述高级特性时再换 ReentrantLock
八、性能特征与瓶颈
性能模型
无竞争路径(最优):
CAS(0→1) 成功 → 持锁
耗时:~10ns(单次 CAS)
轻度竞争(有等待但短暂):
CAS 失败 → 短暂自旋 → CAS 成功
耗时:~100ns~1μs
重度竞争(需要挂起):
入队 → park → unpark → 重新 CAS
耗时:~10μs~100μs(涉及线程上下文切换)
已知性能瓶颈
瓶颈一:虚拟线程 pin(Java 21 重要问题)
synchronized 块内执行 I/O 或长时间操作 → pin 平台线程
ReentrantLock(基于 AQS park)→ 不 pin,虚拟线程可被卸载
⚠️ 但:ReentrantLock 在某些路径上也可能间接触发 pin
(如 Condition.await 内部触发 synchronized)
Java 21 推荐:在虚拟线程中用 ReentrantLock 替代 synchronized
JEP 444 文档明确建议此替换
瓶颈二:公平锁的吞吐量损失
公平锁强制线程切换:
每次 unlock 必须唤醒队列中的下一个线程(park/unpark)
即使当前线程可以立即重新获取锁
非公平锁允许"连续持锁":
A 释放 → A 立即重新获取(无需切换)→ 减少上下文切换
代价是 B 可能等待更久(饥饿风险)
实测:公平锁吞吐量比非公平锁低 20%~40%(随竞争激烈程度变化)
瓶颈三:虚假唤醒与重试开销
park() 存在虚假唤醒(spurious wakeup)
AQS 通过循环 + 状态检查处理:
while (!tryAcquire(arg)) { park(); }
高竞争下:一次 park/unpark 之后可能再次失败重新排队,
造成额外的上下文切换开销
优化建议
① 缩短临界区:锁内只放最必要的操作
② 读多写少 → 换 StampedLock 或 ReentrantReadWriteLock
③ 无竞争预期 → 优先 synchronized(JVM 偏向锁)
④ 虚拟线程场景 → 优先 ReentrantLock(避免 pin)
⑤ 监控等待队列长度:lock.getQueueLength() > 阈值 → 告警
⑥ 避免在锁内做 I/O、RPC 等高延迟操作
九、最佳实践
9.1 标准使用模板(不可省略 finally)
// ✅ 标准模板:lock() 和 unlock() 必须配对
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
// 临界区
} finally {
lock.unlock(); // finally 保证一定执行,防止死锁
}
// ❌ 错误:lock 在 try 内部(若 lock() 本身抛异常,unlock 仍会执行)
try {
lock.lock(); // lock() 极少抛异常,但 lock 应在 try 外
...
} finally {
lock.unlock();
}
9.2 可中断与超时
// 可中断:允许外部取消等待
try {
lock.lockInterruptibly();
try { /* 临界区 */ } finally { lock.unlock(); }
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 恢复中断标志!
// 降级处理
}
// 超时:快速失败
if (!lock.tryLock(100, TimeUnit.MILLISECONDS)) {
// 超时,快速失败或降级
return;
}
try { /* 临界区 */ } finally { lock.unlock(); }
9.3 条件变量规范用法
// Condition 必须在持锁状态下调用 await/signal
Condition condition = lock.newCondition();
// 等待方:必须用 while,不能用 if(防止虚假唤醒)
lock.lock();
try {
while (!conditionMet()) { // ✅ while 循环检查
condition.await();
}
// 执行业务
} finally { lock.unlock(); }
// 通知方
lock.lock();
try {
changeState();
condition.signal(); // 精确唤醒一个
// 或 condition.signalAll(); // 唤醒所有
} finally { lock.unlock(); }
9.4 可观测性与监控
ReentrantLock lock = new ReentrantLock();
// 诊断方法(生产监控)
lock.isLocked(); // 是否被某线程持有
lock.isHeldByCurrentThread(); // 是否被当前线程持有
lock.getHoldCount(); // 当前线程重入次数
lock.getQueueLength(); // 等待队列估计长度
lock.hasQueuedThreads(); // 是否有线程在等待
lock.getWaitQueueLength(cond);// 某 Condition 等待线程数
// 推荐:集成 Micrometer 暴露等待队列长度
Gauge.builder("lock.queue.length", lock, ReentrantLock::getQueueLength)
.tag("name", "myLock")
.register(meterRegistry);
9.5 重入计数管理(防止重入泄漏)
// ⚠️ 重入了几次,就必须 unlock 几次
lock.lock(); // holdCount = 1
lock.lock(); // holdCount = 2
try {
...
lock.lock(); // holdCount = 3
try { ... } finally { lock.unlock(); } // holdCount = 2
} finally {
lock.unlock(); // holdCount = 1
lock.unlock(); // holdCount = 0 → 真正释放
}
// ⚠️ 常见错误:异常路径少执行了一次 unlock → 锁永远不会释放 → 死锁
十、代码示例(Java 21)
import java.util.concurrent.*;
import java.util.concurrent.locks.*;
import java.util.*;
/**
* ReentrantLock Java 21 综合示例
* 覆盖:基础用法、超时锁、可中断锁、多Condition、重入、虚拟线程
*/
public class ReentrantLockDemo {
// ====== 场景一:基础互斥锁 ======
static class SafeCounter {
private final ReentrantLock lock = new ReentrantLock();
private int count = 0;
public void increment() {
lock.lock(); // 加锁
try {
count++;
} finally {
lock.unlock(); // 必须在 finally 中释放
}
}
public int get() {
lock.lock();
try { return count; } finally { lock.unlock(); }
}
}
// ====== 场景二:tryLock 超时 + 防死锁 ======
static void tryLockDemo() throws InterruptedException {
ReentrantLock lockA = new ReentrantLock();
ReentrantLock lockB = new ReentrantLock();
Runnable task = () -> {
while (true) {
// 尝试同时获取两把锁,超时则全部释放,避免死锁
boolean gotA = false, gotB = false;
try {
gotA = lockA.tryLock(50, TimeUnit.MILLISECONDS);
gotB = lockB.tryLock(50, TimeUnit.MILLISECONDS);
if (gotA && gotB) {
System.out.println(Thread.currentThread().getName()
+ " 同时持有 A 和 B,执行转账");
Thread.sleep(10);
break; // 成功,退出循环
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
} finally {
// 无论是否成功,必须释放已获取的锁
if (gotB) lockB.unlock();
if (gotA) lockA.unlock();
}
// 随机退避,减少活锁
Thread.sleep(ThreadLocalRandom.current().nextInt(10));
}
};
Thread t1 = new Thread(task, "Thread-A");
Thread t2 = new Thread(task, "Thread-B");
t1.start(); t2.start();
t1.join(); t2.join();
}
// ====== 场景三:lockInterruptibly 可中断等待 ======
static void interruptibleLockDemo() {
ReentrantLock lock = new ReentrantLock();
lock.lock(); // 主线程持锁,不释放
Thread waiter = new Thread(() -> {
try {
System.out.println("子线程尝试获锁...");
lock.lockInterruptibly(); // 可被中断
try {
System.out.println("子线程获锁成功");
} finally {
lock.unlock();
}
} catch (InterruptedException e) {
// 被中断后走这里,而非无限等待
System.out.println("子线程被中断,放弃获锁");
Thread.currentThread().interrupt(); // 恢复中断标志
}
});
waiter.start();
try { Thread.sleep(100); } catch (InterruptedException e) { /* ignore */ }
waiter.interrupt(); // 中断等待中的子线程
lock.unlock(); // 主线程释放锁
}
// ====== 场景四:多 Condition 实现有界队列 ======
static class BoundedQueue<T> {
private final Queue<T> queue = new LinkedList<>();
private final int capacity;
private final ReentrantLock lock = new ReentrantLock();
// 两个独立的 Condition:精确唤醒,不会互相干扰
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
BoundedQueue(int capacity) { this.capacity = capacity; }
public void put(T item) throws InterruptedException {
lock.lock();
try {
while (queue.size() >= capacity) {
notFull.await(); // 队满,生产者等待(必须用 while)
}
queue.offer(item);
notEmpty.signal(); // 通知消费者,精确唤醒,不打扰其他生产者
} finally { lock.unlock(); }
}
public T take() throws InterruptedException {
lock.lock();
try {
while (queue.isEmpty()) {
notEmpty.await(); // 队空,消费者等待(必须用 while)
}
T item = queue.poll();
notFull.signal(); // 通知生产者,精确唤醒
return item;
} finally { lock.unlock(); }
}
}
// ====== 场景五:可重入演示 ======
static class ReentrantDemo {
private final ReentrantLock lock = new ReentrantLock();
public void outer() {
lock.lock(); // holdCount = 1
try {
System.out.println("outer: holdCount=" + lock.getHoldCount()); // 1
inner(); // 递归调用,同一线程再次加锁
} finally {
lock.unlock(); // holdCount = 0,真正释放
}
}
private void inner() {
lock.lock(); // holdCount = 2(重入,不死锁)
try {
System.out.println("inner: holdCount=" + lock.getHoldCount()); // 2
} finally {
lock.unlock(); // holdCount = 1
}
}
}
// ====== 场景六:Java 21 虚拟线程下的 ReentrantLock ======
static void virtualThreadDemo() throws InterruptedException {
// Java 21:虚拟线程中使用 ReentrantLock 不会 pin 平台线程
// 对比 synchronized:在 synchronized 块内 park 会 pin 平台线程
ReentrantLock lock = new ReentrantLock();
int[] counter = {0};
int taskCount = 10_000;
CountDownLatch latch = new CountDownLatch(taskCount);
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < taskCount; i++) {
executor.submit(() -> {
lock.lock();
try {
counter[0]++;
// 模拟 I/O(虚拟线程不 pin,平台线程可被复用)
Thread.sleep(1);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock();
latch.countDown();
}
});
}
}
latch.await();
System.out.println("虚拟线程计数结果: " + counter[0]); // 10000
}
// ====== 场景七:监控与诊断 ======
static void monitorDemo() throws InterruptedException {
ReentrantLock lock = new ReentrantLock(false);
Condition cond = lock.newCondition();
Thread waiter = new Thread(() -> {
lock.lock();
try { cond.await(1, TimeUnit.SECONDS); }
catch (InterruptedException e) { Thread.currentThread().interrupt(); }
finally { lock.unlock(); }
});
waiter.start();
Thread.sleep(50);
// 诊断
System.out.println("isLocked: " + lock.isLocked());
System.out.println("queueLength: " + lock.getQueueLength());
System.out.println("waitQueueLength: " + lock.getWaitQueueLength(cond));
waiter.join();
}
public static void main(String[] args) throws Exception {
System.out.println("=== 基础计数 ===");
SafeCounter counter = new SafeCounter();
var threads = new Thread[10];
for (int i = 0; i < 10; i++)
threads[i] = new Thread(() -> { for (int j=0; j<1000; j++) counter.increment(); });
for (var t : threads) t.start();
for (var t : threads) t.join();
System.out.println("count = " + counter.get()); // 10000
System.out.println("\n=== 可重入 ===");
new ReentrantDemo().outer();
System.out.println("\n=== 可中断锁 ===");
interruptibleLockDemo();
System.out.println("\n=== tryLock 防死锁 ===");
tryLockDemo();
System.out.println("\n=== 多 Condition 有界队列 ===");
BoundedQueue<Integer> queue = new BoundedQueue<>(3);
new Thread(() -> { try { for (int i=1; i<=5; i++) { queue.put(i); System.out.println("put: "+i); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }).start();
new Thread(() -> { try { for (int i=0; i<5; i++) { Thread.sleep(50); System.out.println("take: "+queue.take()); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }).start();
Thread.sleep(500);
System.out.println("\n=== 监控诊断 ===");
monitorDemo();
System.out.println("\n=== Java 21 虚拟线程 ===");
virtualThreadDemo();
}
}
十一、面试高频考点
原理类
Q1:ReentrantLock 的可重入如何实现?
tryAcquire中判断:若getExclusiveOwnerThread() == currentThread,则state + 1(无需 CAS,已持锁的线程是唯一写者)。tryRelease中state - 1,只有降为 0 时才清除ownerThread并返回true,触发后继唤醒。
Q2:公平锁和非公平锁底层差异在哪一行代码?
差异在
tryAcquire:非公平锁先CAS(0,1)直接抢,成功则持锁(忽略队列);公平锁先调hasQueuedPredecessors(),若队列中有前驱则直接返回false(入队等待),不插队。仅这一个判断,带来了完全不同的调度语义。
Q3:AQS 的 CLH 队列为什么是双向链表?
原始 CLH 是单向链表,通过自旋前驱状态来决定是否获取资源,适合多处理器自旋场景。AQS 改为 park/unpark 机制(避免自旋浪费 CPU),需要从后向前找有效前驱(清理
CANCELLED节点时),因此需要prev指针;next指针用于release后精确唤醒后继,所以是双向。
Q4:为什么 unlock() 中 setState() 不需要 CAS?
setState()是 volatile 写,写语义保证原子性(int 写是原子的)。更重要的是,setState只在tryRelease中调用,而tryRelease入口处已经验证了getExclusiveOwnerThread() == currentThread,即只有持锁线程才能执行此处,不存在并发写竞争,CAS 是多余的。
Q5:Condition.await() 为什么必须在 lock 内调用?
await()的第一步是"完全释放锁"(savedState = enableWait()),若不持锁则无法释放,IllegalMonitorStateException。另外,Condition 队列的操作(addConditionWaiter)本身不是原子的,必须在锁的保护下执行才安全。这和Object.wait()必须在synchronized块内调用是完全类似的语义。
对比类
Q6:什么场景下 ReentrantLock 优于 synchronized?
需要以下特性之一时:① 响应中断(
lockInterruptibly);② 超时获取(tryLock(time));③ 多个条件变量精确唤醒(newCondition());④ 公平锁(new ReentrantLock(true));⑤ 需要监控等待队列(getQueueLength());⑥ 虚拟线程场景中避免 synchronized 导致的平台线程 pin。
Q7:ReentrantLock 和 StampedLock 如何选择?
ReentrantLock:通用互斥,可重入,API 丰富,适合大多数场景。StampedLock:读多写少的极致性能场景,提供乐观读(无锁),但不可重入、不支持 Condition,使用复杂(需处理 stamp 失效重试)。简单场景用ReentrantLock,读性能是瓶颈时考虑StampedLock。
场景类
Q8:Condition.await() 为什么要用 while 而不是 if?
存在两种情况需要循环检查:① 虚假唤醒(spurious wakeup):
park()可能在没有被unpark()的情况下返回(JVM/OS 规范允许);② 条件竞争:signal()后、当前线程重新获锁前,其他线程可能已经修改了条件使其不再满足。if只检查一次不够安全,while重新获锁后再次验证条件才正确。
Q9:一个线程 lock() 了两次但只 unlock() 一次,会发生什么?
state从 2 减为 1,exclusiveOwnerThread仍是当前线程,锁没有被释放。其他所有等待该锁的线程将永远阻塞(死锁)。这是ReentrantLock相比synchronized更危险的地方——synchronized退出块自动释放,ReentrantLock需要人工保证重入次数匹配。
Q10:Java 21 中虚拟线程使用 ReentrantLock 和 synchronized 有何区别?
synchronized在等待时(如Object.wait())会将虚拟线程 pin(固定) 到平台线程,导致平台线程无法被其他虚拟线程复用,削弱虚拟线程的扩展性优势。ReentrantLock.lock()获取失败后调用LockSupport.park(),虚拟线程被挂起并从平台线程卸载,平台线程可以继续运行其他虚拟线程。JEP 444 文档中明确建议:在虚拟线程密集型代码中,用ReentrantLock替换synchronized。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)