ReentrantLock 详解
synchronized 能做的它都能做,synchronized 做不了的它也能做。
基本概念
ReentrantLock(可重入锁)是 Lock 接口最常用的实现,意思是同一线程可以反复获取同一把锁。
java
ReentrantLock lock = new ReentrantLock();
lock.lock(); // 第一次
lock.lock(); // 第二次(可重入)
lock.lock(); // 第三次
try {
// ...
} finally {
lock.unlock(); // count = 2
lock.unlock(); // count = 1
lock.unlock(); // count = 0,锁释放
}与 synchronized 对比
| 特性 | synchronized | ReentrantLock |
|---|---|---|
| 获取/释放 | 自动 | 手动 |
| 公平/非公平 | 非公平 | 可配置 |
| tryLock | ❌ | ✅ |
| 超时 | ❌ | ✅ |
| 中断 | ❌ | ✅ |
| 多条件变量 | ❌ | ✅ |
| 可重入 | ✅ | ✅ |
五种获取锁的方式
1. lock():阻塞获取
最基本的方式,获取不到就一直等:
java
Lock lock = new ReentrantLock();
lock.lock();
try {
// 临界区
} finally {
lock.unlock();
}2. tryLock():不阻塞尝试
立即返回,获取成功返回 true,失败返回 false:
java
Lock lock = new ReentrantLock();
if (lock.tryLock()) {
try {
// 获取到了
} finally {
lock.unlock();
}
} else {
// 没获取到,执行其他逻辑
}3. tryLock(timeout):带超时的尝试
等待一段时间,获取不到就放弃:
java
Lock lock = new ReentrantLock();
if (lock.tryLock(5, TimeUnit.SECONDS)) { // 等 5 秒
try {
// 获取到了
} finally {
lock.unlock();
}
} else {
System.out.println("等不及了");
}4. lockInterruptibly():可中断的获取
等待过程中可以被中断:
java
Lock lock = new ReentrantLock();
try {
lock.lockInterruptibly(); // 等锁时可以被 interrupt()
try {
// 临界区
} finally {
lock.unlock();
}
} catch (InterruptedException e) {
// 被中断了
}5. newCondition():条件变量
创建条件变量,实现更灵活的等待/通知:
java
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
lock.lock();
try {
while (!conditionMet) {
condition.await(); // 等待
}
// 继续执行
condition.signal(); // 通知
} finally {
lock.unlock();
}代码示例
示例一:基本计数器
java
public class Counter {
private final ReentrantLock lock = new ReentrantLock();
private int count = 0;
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
public int get() {
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
}示例二:tryLock 避免死锁
java
public class AccountTransfer {
private final ReentrantLock lock = new ReentrantLock();
private int balance = 0;
// 转账:同时操作两个账户
public boolean transfer(AccountTransfer target, int amount) {
// 尝试获取本账户锁,最多等 1 秒
if (!this.lock.tryLock(1, TimeUnit.SECONDS)) {
return false; // 超时放弃
}
try {
// 尝试获取目标账户锁,最多等 1 秒
if (!target.lock.tryLock(1, TimeUnit.SECONDS)) {
return false;
}
try {
if (this.balance >= amount) {
this.balance -= amount;
target.balance += amount;
return true;
}
return false;
} finally {
target.lock.unlock();
}
} finally {
this.lock.unlock();
}
}
}示例三:可中断等待
java
public class InterruptibleTask {
private final ReentrantLock lock = new ReentrantLock();
public void process() throws InterruptedException {
lock.lockInterruptibly(); // 可以被中断的获取
try {
// 模拟处理
Thread.sleep(5000);
System.out.println("处理完成");
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
InterruptibleTask task = new InterruptibleTask();
Thread t1 = new Thread(() -> {
try {
task.process();
} catch (InterruptedException e) {
System.out.println("线程被中断");
}
}, "T1");
Thread t2 = new Thread(() -> {
try {
task.process();
} catch (InterruptedException e) {
System.out.println("线程被中断");
}
}, "T2");
t1.start();
Thread.sleep(100);
t2.start();
Thread.sleep(500);
t2.interrupt(); // 中断 t2
}
}示例四:查询锁状态
java
public class LockStateDemo {
private final ReentrantLock lock = new ReentrantLock();
public void demo() {
// 获取锁
lock.lock();
try {
System.out.println("是否被锁定: " + lock.isLocked()); // true
System.out.println("当前线程持有: " + lock.isHeldByCurrentThread()); // true
System.out.println("持有次数: " + lock.getHoldCount()); // 1
// 再获取一次(可重入)
lock.lock();
try {
System.out.println("再次获取后,持有次数: " + lock.getHoldCount()); // 2
} finally {
lock.unlock();
}
System.out.println("释放一次后,持有次数: " + lock.getHoldCount()); // 1
System.out.println("等待队列长度: " + lock.getQueueLength()); // 0
} finally {
lock.unlock();
}
}
}示例五:生产者-消费者
java
public class ProducerConsumer {
private final ReentrantLock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
private final Queue<Integer> queue = new LinkedList<>();
private final int capacity = 10;
public void produce(int value) throws InterruptedException {
lock.lock();
try {
while (queue.size() >= capacity) {
notFull.await(); // 队列满,等
}
queue.offer(value);
System.out.println("生产: " + value);
notEmpty.signal(); // 通知队列非空
} finally {
lock.unlock();
}
}
public int consume() throws InterruptedException {
lock.lock();
try {
while (queue.isEmpty()) {
notEmpty.await(); // 队列空,等
}
int value = queue.poll();
System.out.println("消费: " + value);
notFull.signal(); // 通知队列非满
return value;
} finally {
lock.unlock();
}
}
}公平锁 vs 非公平锁
java
// 非公平锁(默认)
ReentrantLock unfairLock = new ReentrantLock();
// 公平锁(按等待顺序)
ReentrantLock fairLock = new ReentrantLock(true);| 对比 | 非公平锁 | 公平锁 |
|---|---|---|
| 吞吐量 | 高 | 低 |
| 等待顺序 | 不保证 | FIFO |
| 饥饿风险 | 存在 | 无 |
默认用非公平锁,因为:
- 性能更好(减少线程切换)
- 公平性在大多数场景不是必需的
用公平锁的场景:需要严格按顺序执行,如任务调度。
总结
ReentrantLock 的五种获取锁方式:
| 方法 | 阻塞 | 超时 | 可中断 | 适用场景 |
|---|---|---|---|---|
lock() | ✅ | ❌ | ❌ | 简单互斥 |
tryLock() | ❌ | ❌ | ❌ | 避免死锁 |
tryLock(t, u) | ❌ | ✅ | ✅ | 超时控制 |
lockInterruptibly() | ✅ | ❌ | ✅ | 可中断等待 |
newCondition() | - | - | - | 条件等待 |
记住:lock 必须在 finally 中释放!
