ReentrantLock(可重入锁) 是 Java 中比 synchronized 更灵活的锁工具,你可以把它想象成 “智能门禁卡” ,允许同一个线程多次进入(可重入),还能设置排队规则(公平锁),甚至设置超时或中断等待。下面用最直白的话拆解它的核心特性:
一、核心特点
-
可重入
- 同一线程 可以重复获取同一把锁,避免自己卡死自己。
- 示例:递归方法中多次加锁。
lock.lock(); try { // 方法A内再次获取锁 lock.lock(); try { /* ... */ } finally { lock.unlock(); } } finally { lock.unlock(); } -
公平性选择
-
公平锁:先到先得,严格按请求顺序分配锁(性能较低)。
-
非公平锁:允许插队,提高吞吐量(默认模式)。
ReentrantLock fairLock = new ReentrantLock(true); // 公平锁 -
-
灵活控制
- 尝试获取锁:
tryLock()立即返回是否成功。 - 超时等待:
tryLock(5, TimeUnit.SECONDS)。 - 中断等待:
lockInterruptibly()允许外部打断线程等待。
- 尝试获取锁:
二、与 synchronized 的对比
| 对比项 | ReentrantLock | synchronized |
|---|---|---|
| 锁获取方式 | 手动 lock() 和 unlock() | 自动获取和释放(代码块结束) |
| 可中断 | ✅ 支持 | ❌ 不支持 |
| 公平性 | ✅ 可设置公平锁 | ❌ 非公平锁 |
| 条件变量 | ✅ 支持多个 Condition | ❌ 只有一个等待队列 |
三、使用场景
-
需要精细控制锁的获取和释放
ReentrantLock lock = new ReentrantLock(); lock.lock(); try { // 临界区代码 } finally { lock.unlock(); // 必须手动释放,防止死锁 } -
处理复杂线程协作(如生产者-消费者)
Condition notFull = lock.newCondition(); // 队列未满条件 Condition notEmpty = lock.newCondition(); // 队列非空条件 // 生产者:队列满时等待 public void produce() { lock.lock(); try { while (queue.isFull()) { notFull.await(); // 释放锁并等待 } queue.add(item); notEmpty.signal(); // 唤醒消费者 } finally { lock.unlock(); } } -
避免死锁(可中断、超时)
if (lock.tryLock(3, TimeUnit.SECONDS)) { try { /* ... */ } finally { lock.unlock(); } } else { // 超时未获取锁,执行其他逻辑 }
四、注意事项
-
必须手动释放锁:
- 忘记
unlock()会导致锁泄漏,其他线程永久等待。 - 务必在
finally块中释放锁。
- 忘记
-
避免嵌套过深:
- 重入次数过多可能导致逻辑复杂,难以维护。
-
公平锁慎用:
- 公平锁性能较低,仅在严格要求顺序时使用。
五、性能优化技巧
- 减少锁粒度:锁的范围尽量小。
- 用
tryLock()替代阻塞获取:避免线程长时间等待。 - 合理使用读写锁(
ReentrantReadWriteLock) :读多写少时更高效。
六、总结
ReentrantLock 是 synchronized 的增强版:
- 优势:可中断、超时、公平锁、条件变量。
- 代价:需手动管理锁,代码稍复杂。
适用场景:
- 需要更细粒度的锁控制。
- 高并发且需要公平性的场景。
- 复杂线程协作(如多条件等待)。
口诀:
「ReentrantLock 真灵活,可重入来可中断
公平非公平随意选,条件变量更便捷
手动加锁要牢记,finally里解锁别忘记!」