在Java里如何使用ReentrantLock实现显式锁_Java锁机制工具解析

ReentrantLock 是 synchronized 的补充而非替代,仅在需中断等待、超时获取、公平调度或多个 Condition 时使用;必须配 try-finally 确保 unlock(),且 Condition 使用须持锁调用。

ReentrantLock 不是 synchronized 的替代品,而是补充——它在需要中断等待、超时获取、公平调度或绑定多个条件时才真正有用。

什么时候该用 ReentrantLock 而不是 synchronized

多数场景下,synchronized 更简单安全;只有明确需要以下能力时,才考虑 ReentrantLock

  • 线程在等待锁时可被 interrupt() 中断(synchronized 遇到中断会继续阻塞)
  • 尝试获取锁但不想无限等待,比如用 tryLock(1, TimeUnit.SECONDS) 设置超时
  • 需要按 FIFO 顺序调度等待线程(启用公平模式:new ReentrantLock(true)
  • 一个锁对应多个 Condition 实例(如生产者/消费者各自 await 不同条件)

lock() / unlock() 必须成对出现在 try-finally

忘记在 finally 块中调用 unlock() 是最常见且致命的错误,会导致锁永远无法释放,后续线程全部阻塞。

ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
    // 临界区操作
    doSomething();
} finally {
    lock.unlock(); // 这一行绝不能少,也不能放在 try 或 catch 里
}

注意:lock.lockInterruptibly() 可被中断,但同样要配 finally;而 tryLock() 成功后也必须 unlock(),失败则无需 unlock。

Condition 的使用和陷阱

ReentrantLock 自带 newCondition() 方法,返回的 ConditionObject.wait()/notify() 更灵活,但规则更严格:

  • 必须由持有该锁的线程调用 await(),否则抛 IllegalMonitorStateException
  • signal() 不会释放锁,只唤醒等待线程;被唤醒线程仍需重新竞争锁
  • 一个 Condition 实例只能用于一个锁,不可跨锁复用
  • 避免用 signalAll() 在高并发下引发“惊群效应”,优先考虑精准 signal()
ReentrantLock lock = new ReentrantLock();
Condition notFull = lock.newCondition();
Condition notEmp

ty = lock.newCondition(); // 生产者 lock.lock(); try { while (isFull()) { notFull.await(); // 等待非满 } addToBuffer(item); notEmpty.signal(); // 唤醒一个消费者 } finally { lock.unlock(); }

公平性开关不是性能优化项,而是语义选择

构造时传 true 启用公平模式:new ReentrantLock(true),意味着锁按等待时间顺序分配。但这会显著降低吞吐量——因为每次都要遍历同步队列。

  • 默认非公平模式(false 或无参构造)允许插队:刚释放锁的线程可能立刻再次抢到,减少上下文切换
  • 公平模式仅在业务逻辑**强依赖执行顺序**时才启用,例如任务调度器、资源配额系统
  • 不要为了“看起来更合理”而开启公平模式,它不解决死锁,也不保证实时性

锁的重入、可监控(getHoldCount()isLocked())、可中断这些特性,都是以额外字段和 CAS 开销换来的——用不用,得看真实需求是否压倒这点成本。