Skip to content

Synchronized 和 ReentrantLock 有什么区别? ​

  • 标签:Java并发、Java

🏛 业务架构师 ​

两者都是可重入互斥锁,差异我按业务决策维度讲:

  • 实现层:synchronized 是 JVM 内置(monitorenter/monitorexit,锁升级:偏向→轻量级→重量级);ReentrantLock 是 JDK API 层(AQS 实现);
  • 功能差异:ReentrantLock 提供 synchronized 没有的三件事——tryLock 超时获取(拿不到就放弃,防死锁)、公平锁(按等待顺序获取)、Condition 多条件队列(一组锁上多个等待条件,生产者/消费者分别唤醒);
  • 可中断性:ReentrantLock 可响应中断,synchronized 只能硬等。

我的选型原则:默认 synchronized,有明确需求才换。理由是业务工程视角的:

  1. 简单是压倒性优势:synchronized 无需手动释放(异常也不泄漏锁),用错的姿势少;ReentrantLock 忘了 unlock 就是灾难,且代码形态更绕;
  2. 性能已不是差异点:JDK 6 锁升级优化后,常规竞争下两者相当——"性能原因选 RLock"是十年前的答案,别再拿它当理由;
  3. 换 ReentrantLock 的正当理由:需要 tryLock 做超时降级(拿不到锁就走兜底逻辑,交易热点防雪崩的常见设计)、需要公平性、需要多 Condition 精准唤醒(任务调度、缓冲池)。

比选型更重要的是锁的架构用法:能用无锁(CAS/不可变)就不用锁;能小粒度(按订单 ID 分段锁)就不大粒度;锁内不做慢操作(IO/远程调用);高竞争热点先用"本地缓存 + 异步合并写"削峰再谈锁——见过热点账户更新每秒 8 万次,加什么锁都白搭,改成异步合并批量更新才解决。

一句话:锁选型是小事,锁的粒度与锁内动作才是架构级决策。

🏗 系统架构师 ​

两者都是可重入互斥锁,我从五个维度对比并给选型:

  • 实现层面:synchronized 是 JVM 内置关键字(monitorenter/monitorexit 指令,基于对象监视器),锁信息在对象头 Mark Word;ReentrantLock 是 JDK 类库实现,底层 AQS(state 计数 + CLH 队列);
  • 功能差异:ReentrantLock 三大增强——tryLock 超时获取(避免死锁永久等待)、可中断获取 lockInterruptibly(死锁治理利器)、公平锁(构造参数 true,保序防饥饿);synchronized 只有非公平;
  • 条件队列:ReentrantLock 支持多个 Condition(多条件等待唤醒,生产者消费者分队列),synchronized 的 wait/notify 单一等待集,notifyAll 惊群;
  • 性能:JDK 6 后 synchronized 有偏向锁 → 轻量级(CAS 自旋)→ 重量级(park)的锁升级,日常竞争下性能与 ReentrantLock 相当,不再是"性能差"的老印象;
  • 使用安全:synchronized 自动释放(异常退出也释放),ReentrantLock 必须手动 unlock 且放 finally——用错就是灾难,这是它最大的工程成本。

我的选型:默认 synchronized(简单安全够用);需要 tryLock 超时、可中断、公平性、多条件变量时才用 ReentrantLock。另外读多写少考虑 ReadWriteLock/StampedLock,计数场景直接 LongAdder。

💻 全栈开发专家 ​

维度synchronizedReentrantLock
层面JVM 内置(关键字/monitorenter)JDK 类库(AQS 实现)
释放异常/退出自动释放必须 finally 手动 unlock
公平锁只有非公平可选公平/非公平
可中断不可(拿不到就一直等)lockInterruptibly()
超时不支持tryLock(3, TimeUnit.SECONDS)
条件变量一个(wait/notify)多个 Condition 精准唤醒

性能:JDK 6 之后 synchronized 有锁升级(偏向→轻量 CAS→重量监视器锁),日常竞争下两者差距已经很小——"性能差所以用 Lock"是过时结论。

我的选择原则:

  • 默认 synchronized:简单、不会忘释放(忘 unlock 是 Lock 最常见事故);
  • 需要 tryLock 超时避免死锁、需要可中断、需要多条件队列(生产者消费者分开唤醒,两个 Condition 队列 notFull/notEmpty 对应 ArrayBlockingQueue 源码)时用 ReentrantLock。
java
if (lock.tryLock(1, TimeUnit.SECONDS)) {       // 死锁免疫的关键手段
    try { doWork(); } finally { lock.unlock(); }
}

加分点:synchronized 还有字节码层面差异(同步方法用 ACC_SYNCHRONIZED 标志);JDK 15 起偏向锁被废弃(维护成本高于收益),说明内置锁也在持续演化。