Synchronized 和 ReentrantLock 有什么区别?
- 标签:Java并发、Java
🏛 业务架构师
两者都是可重入互斥锁,差异我按业务决策维度讲:
- 实现层:synchronized 是 JVM 内置(monitorenter/monitorexit,锁升级:偏向→轻量级→重量级);ReentrantLock 是 JDK API 层(AQS 实现);
- 功能差异:ReentrantLock 提供 synchronized 没有的三件事——tryLock 超时获取(拿不到就放弃,防死锁)、公平锁(按等待顺序获取)、Condition 多条件队列(一组锁上多个等待条件,生产者/消费者分别唤醒);
- 可中断性:ReentrantLock 可响应中断,synchronized 只能硬等。
我的选型原则:默认 synchronized,有明确需求才换。理由是业务工程视角的:
- 简单是压倒性优势:synchronized 无需手动释放(异常也不泄漏锁),用错的姿势少;ReentrantLock 忘了 unlock 就是灾难,且代码形态更绕;
- 性能已不是差异点:JDK 6 锁升级优化后,常规竞争下两者相当——"性能原因选 RLock"是十年前的答案,别再拿它当理由;
- 换 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。
💻 全栈开发专家
| 维度 | synchronized | ReentrantLock |
|---|---|---|
| 层面 | 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 起偏向锁被废弃(维护成本高于收益),说明内置锁也在持续演化。