Java ReentrantLock 深度解析:为什么它比 synchronized 更灵活?
目录
ReentrantLock 是什么?
ReentrantLock
是 Java 并发包(java.util.concurrent.locks
)中的一种可重入锁,它提供了比 synchronized
更灵活的锁控制能力。
核心特点:
- 手动加锁/释放:需要显式调用
lock()
和unlock()
。 - 可中断:等待锁时可以被其他线程中断。
- 公平锁:支持先到先得的公平锁策略。
- 条件变量:通过
Condition
实现线程间协作。
核心特性与使用场景
1. 可中断的锁获取
场景:线程等待锁时,如果长时间无法获取,允许被外部中断。
代码示例:
ReentrantLock lock = new ReentrantLock();
try {
lock.lockInterruptibly(); // 可中断的加锁
// 执行关键代码...
} catch (InterruptedException e) {
System.out.println("线程被中断,放弃获取锁");
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
2. 公平锁与非公平锁
公平锁:按请求锁的顺序分配(先到先得)。
非公平锁:允许插队(默认策略,吞吐量更高)。
// 创建公平锁
ReentrantLock fairLock = new ReentrantLock(true);
适用场景:
- 公平锁:避免线程饥饿(如高并发支付系统)。
- 非公平锁:追求高吞吐量(多数场景)。
3. 尝试获取锁(带超时)
场景:防止线程无限等待,避免死锁。
if (lock.tryLock(3, TimeUnit.SECONDS)) { // 尝试获取锁,最多等3秒
try {
// 成功获取锁,执行操作
} finally {
lock.unlock();
}
} else {
System.out.println("获取锁超时,执行其他逻辑");
}
4. 条件变量(Condition)
场景:实现线程间精确协作(类似 wait()
/notify()
,但更灵活)。
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
// 线程A:等待条件
lock.lock();
try {
while (!条件满足) {
condition.await(); // 释放锁并等待
}
// 条件满足后继续执行...
} finally {
lock.unlock();
}
// 线程B:通知条件满足
lock.lock();
try {
条件 = true;
condition.signalAll(); // 唤醒所有等待线程
} finally {
lock.unlock();
}
代码实战:银行转账案例
需求:实现两个账户之间的安全转账,避免并发问题。
import java.util.concurrent.locks.ReentrantLock;
class BankAccount {
private final ReentrantLock lock = new ReentrantLock();
private int balance;
public BankAccount(int balance) {
this.balance = balance;
}
// 转账方法
public void transfer(BankAccount target, int amount) {
boolean thisLock = false;
boolean targetLock = false;
try {
// 尝试获取两把锁(避免死锁)
thisLock = this.lock.tryLock(1, TimeUnit.SECONDS);
targetLock = target.lock.tryLock(1, TimeUnit.SECONDS);
if (thisLock && targetLock) {
if (this.balance >= amount) {
this.balance -= amount;
target.balance += amount;
System.out.println("转账成功");
}
} else {
System.out.println("获取锁失败,稍后重试");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
if (thisLock) this.lock.unlock();
if (targetLock) target.lock.unlock();
}
}
}
与 synchronized 的对比
特性 | ReentrantLock | synchronized |
---|---|---|
锁获取方式 | 手动 (lock() /unlock() ) | 自动(代码块结束释放) |
可中断 | 支持 | 不支持 |
公平锁 | 支持 | 不支持 |
条件变量 | 支持多个 Condition | 仅一个等待队列 (wait() ) |
性能 | 高并发下更优(需实测) | JDK 优化后接近 |
代码复杂度 | 高(需处理异常和释放锁) | 低(自动管理) |
最佳实践与常见陷阱
1. 必须释放锁
- 错误示例:忘记调用
unlock()
,导致死锁。 - 正确做法:在
finally
块中释放锁。
2. 避免嵌套锁
- 问题:多个锁嵌套获取时容易死锁。
- 解决方案:按固定顺序获取锁,或使用
tryLock()
超时机制。
3. 慎用公平锁
- 陷阱:公平锁降低吞吐量(上下文切换增加)。
- 建议:仅在明确需要公平性时使用。
总结
何时选择 ReentrantLock?
- 需要可中断、超时获取锁。
- 需要公平锁策略。
- 需要复杂的线程协作(多个条件变量)。
何时选择 synchronized?
- 简单同步需求,代码简洁优先。
- JDK 5 之前性能敏感场景(现代 JDK 差距已缩小)。
核心思想:根据需求权衡灵活性与复杂度!