Java并发编程避坑指南:ReentrantLock的tryLock()和Condition你用对了吗?
Java并发编程避坑指南:ReentrantLock的tryLock()和Condition实战精要
在分布式系统和高并发场景成为主流的今天,Java并发编程能力已成为中高级开发者的必备技能。ReentrantLock作为synchronized的增强版,提供了更灵活的锁控制机制,但这也意味着更高的使用门槛。本文将深入剖析ReentrantLock中tryLock()和Condition这两个高级特性的典型误区和最佳实践。
1. tryLock()的陷阱与正确打开方式
tryLock()方法看似简单,但实际项目中因错误使用导致的线程安全问题比比皆是。与无条件获取锁的lock()不同,tryLock()提供了尝试获取锁的能力,这既是优势也是隐患。
1.1 时间参数被忽略的灾难
最常见的错误是忽略tryLock(long timeout, TimeUnit unit)的时间参数,导致线程饥饿。看这段典型问题代码:
ReentrantLock lock = new ReentrantLock(); if (lock.tryLock()) { // 问题点:未设置超时时间 try { // 业务逻辑 } finally { lock.unlock(); } } else { // 备用逻辑 }改进方案应始终指定合理的超时时间:
if (lock.tryLock(300, TimeUnit.MILLISECONDS)) { try { // 业务逻辑 } finally { lock.unlock(); } } else { // 记录超时日志或执行降级策略 }1.2 返回值检查缺失
另一个高频错误是忽略tryLock()的返回值检查:
lock.tryLock(); // 警告:未检查返回值 // 直接执行业务逻辑,可能导致并发问题正确的做法应该是:
if (lock.tryLock(100, TimeUnit.MILLISECONDS)) { try { // 受保护的代码块 } finally { lock.unlock(); } } else { throw new BusyException("系统繁忙,请稍后重试"); }2. Condition的精准通知机制
Condition相比传统的wait()/notify()提供了更精细的线程通信控制,但使用不当同样会引发问题。
2.1 signal()与signalAll()的选择困境
考虑这个生产者-消费者场景:
class Buffer { private final Lock lock = new ReentrantLock(); private final Condition notFull = lock.newCondition(); private final Condition notEmpty = lock.newCondition(); private Queue<String> queue = new LinkedList<>(); private int capacity = 10; public void put(String item) throws InterruptedException { lock.lock(); try { while (queue.size() == capacity) { notFull.await(); } queue.add(item); notEmpty.signal(); // 关键决策点 } finally { lock.unlock(); } } }选择原则:
- 使用
signal()当且仅当每次只唤醒一个线程就能保证正确性 - 在状态变化可能影响多个等待线程时,必须使用
signalAll() - 在生产者-消费者模式中,通常可以使用
signal()提高性能
2.2 条件谓词与while循环
绝对不要用if代替while来检查条件谓词:
// 危险代码 if (queue.isEmpty()) { condition.await(); } // 正确写法 while (queue.isEmpty()) { condition.await(); }这是因为:
- 虚假唤醒可能发生(即使没有调用
signal()) - 其他线程可能在
await()返回后但重新获取锁之前修改了状态 - Java语言规范明确建议在
await()前后检查条件
3. 锁释放的终极保障方案
忘记在finally块中释放锁是ReentrantLock使用中最危险的错误。我们推荐这种模板代码:
Lock lock = new ReentrantLock(); try { if (lock.tryLock(500, TimeUnit.MILLISECONDS)) { try { // 临界区代码 } finally { lock.unlock(); // 内层finally确保锁释放 } } } catch (InterruptedException e) { Thread.currentThread().interrupt(); // 恢复中断状态 throw new RuntimeException("获取锁被中断", e); } finally { if (lock.isHeldByCurrentThread()) { // 防御性检查 lock.unlock(); // 外层双重保险 } }关键检查点:
- 每个
lock()或tryLock()必须对应一个unlock() - 在finally块中释放锁
- 考虑使用
isHeldByCurrentThread()做防御性检查 - 正确处理
InterruptedException
4. 性能优化与监控实践
4.1 锁竞争监控技巧
通过ReentrantLock的扩展方法监控锁状态:
ReentrantLock lock = new ReentrantLock(); // 获取等待队列长度 int queuedThreads = lock.getQueueLength(); // 获取锁是否被持有 boolean isLocked = lock.isLocked(); // 在诊断日志中记录这些信息 log.debug("锁状态:持有={}, 等待线程={}", isLocked, queuedThreads);4.2 公平锁与非公平锁的选择
// 非公平锁(默认) ReentrantLock unfairLock = new ReentrantLock(); // 公平锁 ReentrantLock fairLock = new ReentrantLock(true);选择策略:
- 非公平锁:吞吐量优先,线程饥饿风险
- 公平锁:响应时间优先,吞吐量降低约10-15%
- 默认推荐非公平锁,除非有明确的公平性要求
4.3 锁分段技术应用
对于高竞争场景,考虑锁分段:
class SegmentedCounter { private final int segments = 16; private final ReentrantLock[] locks = new ReentrantLock[segments]; private final int[] counts = new int[segments]; public SegmentedCounter() { for (int i = 0; i < segments; i++) { locks[i] = new ReentrantLock(); } } public void increment(int key) { int segment = key % segments; locks[segment].lock(); try { counts[segment]++; } finally { locks[segment].unlock(); } } }5. 复杂场景下的Condition应用模式
5.1 多条件变量协作
class TaskCoordinator { private final Lock lock = new ReentrantLock(); private final Condition taskReady = lock.newCondition(); private final Condition resultReady = lock.newCondition(); private boolean isTaskPrepared = false; private boolean isResultProcessed = false; public void prepareTask() throws InterruptedException { lock.lock(); try { // 准备任务逻辑 isTaskPrepared = true; taskReady.signalAll(); while (!isResultProcessed) { resultReady.await(); } } finally { lock.unlock(); } } public void processResult() throws InterruptedException { lock.lock(); try { while (!isTaskPrepared) { taskReady.await(); } // 处理结果逻辑 isResultProcessed = true; resultReady.signalAll(); } finally { lock.unlock(); } } }5.2 超时等待模式
public boolean awaitWithTimeout(Condition condition, long timeout) throws InterruptedException { long remaining = timeout; long end = System.nanoTime() + remaining; while (true) { try { return condition.await(remaining, TimeUnit.NANOSECONDS); } catch (InterruptedException ie) { if (System.nanoTime() - end >= 0) { Thread.currentThread().interrupt(); return false; } remaining = end - System.nanoTime(); } } }