在并发编程中,我们常用 synchronized 来实现线程同步。但在更复杂的场景下,Lock 接口提供了更强大而灵活的控制方式,特别是它的实现类 —— ReentrantLock。本文将带你全面掌握它的原理、用法和注意事项。
一、为什么需要 Lock 接口?
synchronized 虽然简单,但它存在以下局限:
不能中断等待线程
不能实现超时锁
不能尝试获取锁
不支持公平性策略
无法实现条件变量
而 Lock 接口弥补了这些不足,提供了可中断、公平、尝试获取等高级功能,成为并发控制的重要工具。
二、Lock 接口核心方法
public interface Lock {
void lock(); // 获取锁(可能阻塞)
void lockInterruptibly() // 可响应中断的获取锁
boolean tryLock(); // 尝试获取锁(立即返回)
boolean tryLock(long time, TimeUnit unit); // 超时获取锁
void unlock(); // 释放锁
Condition newCondition(); // 创建条件变量
}
这些方法使得锁的控制不再是“黑箱”,而是完全由开发者掌控。
三、ReentrantLock 的基本用法
Lock lock = new ReentrantLock();
try {
lock.lock();
// 临界区代码
} finally {
lock.unlock();
}
必须使用 try-finally 保证锁最终释放,避免死锁风险。
四、ReentrantLock 实现原理简析
可重入性
所谓可重入,即一个线程获得锁之后,可以再次获得该锁而不会被阻塞。这是通过内部的 holdCount 计数器实现的。
内部机制
ReentrantLock 基于 AQS(AbstractQueuedSynchronizer) 实现。
AQS 维护一个 CLH 队列,通过 CAS 操作控制锁状态。
公平/非公平策略通过 AQS 的不同实现控制。
五、lock vs synchronized 对比
特性
synchronized
ReentrantLock
可重入
✅
✅
公平锁
❌
✅
中断响应
❌
✅
条件变量
❌
✅(Condition)
自动释放锁
✅
❌(必须手动 unlock)
六、Condition 条件变量用法
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
lock.lock();
try {
while (!条件成立) {
condition.await(); // 当前线程等待
}
// 条件满足,执行后续逻辑
} finally {
lock.unlock();
}
使用 signal() 或 signalAll() 通知等待线程继续执行,类似 Object.wait/notify,但更灵活。
七、实践建议与最佳实践
一定要在 finally 中释放锁,防止死锁。
只在必要时用 Lock,简单场景优先用 synchronized。
使用 tryLock() 避免长期阻塞,提升系统健壮性。
利用 Condition 拆分多个等待队列,提升控制精度。
八、可视化理解:ReentrantLock 的线程阻塞流程
ReentrantLock 通过链式队列将等待线程串联起来,确保公平与顺序执行。
九、实战场景:银行转账线程安全示例
public void transfer(Account from, Account to, int amount) {
Lock lock1 = from.getLock();
Lock lock2 = to.getLock();
// 避免死锁的锁定顺序控制
if (System.identityHashCode(lock1) < System.identityHashCode(lock2)) {
lock1.lock(); lock2.lock();
} else {
lock2.lock(); lock1.lock();
}
try {
from.withdraw(amount);
to.deposit(amount);
} finally {
lock2.unlock();
lock1.unlock();
}
}
十、ReentrantLock 底层源码:AQS 架构核心
ReentrantLock 是基于 Java 并发包中的核心抽象类 AbstractQueuedSynchronizer(AQS)构建的。
AQS 核心设计理念:
使用一个 volatile int 变量(state)来表示锁状态,并通过一个 CLH 队列管理所有获取锁失败的线程。
关键成员变量:
abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer {
private volatile int state; // 锁状态:0 = 未锁,>0 = 被锁
private transient volatile Node head; // 队列头
private transient volatile Node tail; // 队列尾
}
十一、ReentrantLock.lock() 的执行流程源码解读
public void lock() {
sync.lock(); // sync 为内部 Sync 实例,公平或非公平子类
}
以非公平锁为例:
static final class NonfairSync extends Sync {
final void lock() {
if (compareAndSetState(0, 1)) // 尝试抢占锁
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1); // 加入等待队列
}
}
AQS.acquire()
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt(); // 响应中断
}
十二、锁的排队机制(CLH 队列)结构解剖
AQS 使用一个双向链表模拟等待线程队列:
head -> Node(Thread-1) <-> Node(Thread-2) <-> ... <-> tail
当线程竞争失败时,进入队列:
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 若失败则 full enqueue
enq(node);
}
十三、tryAcquire() 的实现
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 未上锁,尝试竞争
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
} else if (current == getExclusiveOwnerThread()) {
// 可重入
int nextc = c + acquires;
setState(nextc);
return true;
}
return false;
}
十四、Condition 条件变量:底层实现与源码分析
ConditionObject 是 AQS 的内部类,用于管理等待队列。
public class ConditionObject implements Condition {
private transient Node firstWaiter; // 条件队列头
private transient Node lastWaiter; // 条件队列尾
public final void await() throws InterruptedException {
Node node = addConditionWaiter();
int savedState = fullyRelease(node); // 释放锁
boolean interrupted = false;
// 阻塞直到被 signal
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if (Thread.interrupted()) interrupted = true;
}
acquireQueued(node, savedState); // 恢复锁
if (interrupted) selfInterrupt();
}
public final void signal() {
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
}
使用注意事项:
await() 会释放锁并加入条件等待队列,阻塞线程。
signal() 会将条件队列的线程移至同步队列,并唤醒尝试重新竞争锁。
十五、调试技巧与源码探索建议
1. 打断点追踪 ReentrantLock.lock()
在 IDE(如 IntelliJ IDEA)中跟踪 ReentrantLock.lock() → sync.lock() → AQS.acquire(),可以观察线程在锁竞争中的状态转变。
2. 可视化线程状态
配合 JVM 参数:
-XX:+UnlockDiagnosticVMOptions -XX:+PrintConcurrentLocks
可以使用 jstack 命令分析死锁、锁争用、条件等待等线程状态。
3. JFR(Java Flight Recorder)分析锁竞争
开启 JFR:
java -XX:StartFlightRecording=filename=recording.jfr,duration=60s -jar your-app.jar
使用 JDK Mission Control 可视化分析 Lock 的争用时间和热点代码段。
十六、总结
ReentrantLock 是 Java 并发包的重要基石,而 AQS 则是其强大功能的核心支撑。掌握其源码结构与调试技巧,可以帮助你:
更好理解并发控制的本质
定位性能瓶颈或死锁问题
编写高性能、高安全的并发代码