深蓝黑背景品牌封面:居中大标题「synchronized 和 Lock,用哪个」,副标题「实现机制、功能差异与选型决策」;下方装饰——左右两块面板分别标注 synchronized(关键字,自动释放)与 Lock(API,手动控制),中间一个圆形 VS 标志;顶部标注「经典题解析 · Java 后端」与「Day 06 · 09-24」 图 1:本文封面,点明题目与「对比加选型」的主线。

先说明属性:这是一篇经典题解析,题目出自本地历史面试资料(见文末来源说明),与任何公司的当前招聘无关。「synchronized 和 Lock 的区别」是 Java 并发面试的保留曲目,人人都能背出两三条,面试官真正想看的是你能不能把差异组织成一张有结构的对比表,并落到「什么场景用哪个」的决策上。

想象面试现场,面试官问:「synchronized 和 ReentrantLock 都用过吧?说说区别,再说说你的项目里怎么选。」

第一步,审题。

把这题拆成两层:差异在哪、怎么选型。差异不要零散地背,按六个维度对照着讲——出身、用法、中断、超时、公平、条件队列;选型则是一句决策口诀加一个前提:默认用 synchronized,需要 Lock 的独门能力时再换。

第二步,六个维度对照。

出身。synchronized 是 JVM 内置关键字,锁的是每个对象自带的监视器;Lock 是 JDK 的接口(代表实现 ReentrantLock),官方文档开篇即点明定位:Lock 实现提供比 synchronized 方法和语句更广泛的锁操作。一个是语言特性,一个是普通 API,这决定了后面所有差异。

用法。synchronized 进代码块自动加锁、出代码块(包括异常)自动释放;Lock 没有这种块结构保护,官方文档给出必须遵守的惯用法——lock() 之后紧跟 try,业务代码放 try 里,unlock() 放 finally 里。自动释放换来的是省心,手动释放换来的是灵活:锁可以在不同作用域、按任意顺序获取和释放(比如链式遍历时的 hand-over-hand 锁定),这是 synchronized 结构上做不到的。

中断与超时。线程等 synchronized 的锁,拿不到就一直等,不可中断;Lock 提供 lockInterruptibly()(等待时可响应中断)、tryLock()(拿不到立刻返回 false)和 tryLock(long, TimeUnit)(限时等待,超时返回 false)。死锁排查和服务降级里,这三样东西是救命的。

公平。synchronized 是非公平的,没的选;ReentrantLock 的构造器接受可选的公平参数——官方文档说明,公平锁在竞争下倾向把锁授予等待最久的线程,能杜绝线程饿死,代价是整体吞吐量明显更低。注意一个文档里写明的例外:无参 tryLock() 不讲公平,锁空着就抢(barging),要守规矩请用 tryLock(0, TimeUnit.SECONDS)。

条件队列。synchronized 配 wait/notify,一个对象只有一个等待队列,notify 唤醒谁都由不得你;Lock 可以挂多个 Condition,官方文档说明 newCondition() 返回绑定在该锁上的条件实例,等待与唤醒可以分队列进行——生产者消费者里「非空」「非满」两个条件分开唤醒,就靠它。

第三步,一个容易漏的共同点。

讲完全部差异,要主动补一句:官方文档要求所有 Lock 实现必须提供与内置监视器锁完全相同的内存同步语义——成功 lock 与 unlock 的内存效果和监视器的 Lock/Unlock 动作一致。也就是说,两者的可见性保证相同,差异只在功能与灵活性,不在「哪个更正确」。

对比图:左列 synchronized(JVM 内置关键字;自动加锁自动释放;等待不可中断;拿不到锁就一直等;非公平;只有一个条件队列 wait/notify),右列 Lock(JDK 的 API,如 ReentrantLock;finally 里手动 unlock;lockInterruptibly 可响应中断;tryLock 支持限时等待;可选公平锁;可挂多个 Condition),中间一行标注对比维度:出身、用法、中断、超时、公平、条件队列。底部选型决策条:默认用 synchronized——写法简单、JVM 持续优化;需要超时、可中断、公平锁或多个条件队列时才上 Lock 图 2:六维对比表与选型决策条。

第四步,选型决策。

口诀:默认 synchronized。写法简单、不会漏 unlock、JVM 对它持续优化,绝大多数同步场景它就是最优解。什么时候换 Lock:需要超时等待(拿锁失败要降级)、需要可中断(任务取消语义)、需要公平锁(严格 FIFO 防饿死)、需要多个条件队列(精细的等待唤醒)——四个「需要」出现任何一个,就是 ReentrantLock 的主场。反之,仅仅因为「Lock 听起来更高级」而换,是把 finally 漏 unlock 的风险引进门。

易错点清单。

一,用 Lock 却不写 try/finally——异常一路抛出去,锁永远不还,这是 Lock 最经典的线上事故。二,以为公平锁又快又公平——官方文档明说它通常明显更慢,公平是拿吞吐量换的。三,以为 synchronized 性能一定差——JVM 对它有锁消除、锁升级等一系列优化,「synchronized 慢」是过时结论。四,背六条差异却答不出选型——面试官的落点永远是「你的场景用哪个、为什么」。

回到面试现场,建议的答题节奏:先给选型口诀(默认 synchronized,四个需要才上 Lock),再按六维度展开对比,然后补内存语义相同这个共同点,最后举自己项目里一个真实的用锁场景收尾。有结构、有取舍、有实例,这题就答满了。

推理服务与数据管道里的连接池、缓冲队列都涉及锁选型。如果面试官追问「读多写少场景下,比 ReentrantLock 更合适的锁是什么」,你能答上来并说出代价吗?欢迎写出你的答案。

参考资料

  • Lock(JDK 21 官方文档:比 synchronized 更广泛的锁操作、try-finally 惯用法、tryLock/lockInterruptibly/限时 tryLock、多 Condition、与内置监视器锁相同的内存同步语义) — https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/concurrent/locks/Lock.html
  • ReentrantLock(JDK 21 官方文档:与 synchronized 相同的基本行为与语义、可选公平参数及吞吐量代价、无参 tryLock 的 barging 行为、hold count 上限与监控方法) — https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/concurrent/locks/ReentrantLock.html