在Java里ReentrantLock相比synchronized有什么优势_Java锁机制对比说明

ReentrantLock的核心优势是“可控”而非“更快”,支持lockInterruptibly()响应中断、多Condition精准唤醒、tryLock()避免死锁,但需严格配对lock/unlock且公平锁性能差。

ReentrantLock 的核心优势不是“更快”,而是“可控”——它把锁的生命周期、等待策略、唤醒逻辑,全交到你手上。

什么时候必须用 lockInterruptibly()

当线程在等锁时,你希望它能被外部中断(比如服务优雅停机、超时熔断),synchronized 完全做不到。JVM 会忽略中断信号,线程卡在 monitorenter 就是死等。

  • 典型场景:RPC 调用中,下游响应慢,主线程需在 5 秒内放弃等待锁并返回降级结果
  • 错误

    写法:synchronized 块里调用 Thread.interrupt() → 无反应
  • 正确写法:
    public void doWork() throws InterruptedException {
        lock.lockInterruptibly(); // 这里会响应中断
        try {
            // 临界区
        } finally {
            lock.unlock();
        }
    }

为什么需要多个 Condition 而不是只靠 wait()/notifyAll()

一个 synchronized 块只能配一套 wait/notify,所有等待线程挤在一个队列里。而 ReentrantLock 可以按业务语义分组唤醒,避免“惊群效应”。

  • 生产者-消费者模型中:notFull.await()notEmpty.await() 是两个独立等待队列
  • 调用 notFull.signal() 只唤醒正在等“有空间”的生产者,不会误唤醒消费者
  • synchronized 下只能用 notifyAll(),每次都要遍历全部等待线程做条件重判,浪费 CPU

tryLock(long, TimeUnit) 解决什么真实问题?

它不是为了“抢锁快”,而是为了打破死锁僵局或实现非阻塞协作。

  • 常见错误现象:两个线程分别持有 A 锁和 B 锁,又同时尝试获取对方锁 → 死锁
  • tryLock 可主动退让:
    if (lockA.tryLock(100, TimeUnit.MILLISECONDS)) {
        try {
            if (lockB.tryLock(100, TimeUnit.MILLISECONDS)) {
                try {
                    // 同时拿到两把锁
                } finally {
                    lockB.unlock();
                }
            }
        } finally {
            lockA.unlock();
        }
    }
  • 注意:tryLock 失败后必须处理失败路径,不能直接重试(否则变忙等)

公平锁参数 new ReentrantLock(true) 别乱开

公平锁看起来“更合理”,但代价是性能暴跌——所有线程必须排队进 CLH 队列,连自旋机会都没有。

  • 实测数据(JDK 17,4 核):高竞争下公平锁吞吐量比默认非公平锁低 3–5 倍
  • 仅在极少数场景适用:你明确观察到某类线程长期饥饿(比如日志线程总抢不到锁),且能接受吞吐下降
  • synchronized 没有公平选项,它天然是非公平的,这也是它优化空间更大的原因之一

真正容易被忽略的点:用 ReentrantLock 时,lock()unlock() 必须严格成对,且 unlock() 一定要写在 finally 里——哪怕只漏一次,整个系统就可能卡死。而 synchronized 的“自动释放”不是便利性优势,是安全底线。