孤舟笔记 并发篇七 synchronized和Lock到底啥区别?面试为什么年年都问这道题
文章目录
- 先说结论:核心区别一览
- synchronized:自动挡,省心但死板
- Lock:手动挡,灵活但要自己负责
- Condition:Lock 的杀手锏
- synchronized 的逆袭:JVM 层面优化
- synchronized vs Lock 全景
- 回答技巧与点评
- 标准回答
- 加分回答
- 面试官点评
个人网站
学 Java 并发的时候,你一定见过这对"双胞胎"——synchronized和Lock。都是加锁,都能保证线程安全,那为啥要搞两套?面试官还特别喜欢问"它们的区别",你随口说一个"Lock 更灵活",面试官追问"灵活在哪",你就卡壳了。
今天咱们就把 synchronized 和 Lock 放在一起掰开了比,让你面试时能说个清清楚楚。
先说结论:核心区别一览
||| 维度 | synchronized | Lock(ReentrantLock) |
|||------|------|------|
||| 层面 | JVM 关键字 | JDK API 接口 |
||| 加锁方式 | 隐式加锁,进出自动 | 显式加锁,手动 lock/unlock |
||| 可中断性 | 不可中断 | lockInterruptibly() 可中断 |
||| 超时获取 | 不支持 | tryLock(timeout) 支持 |
||| 公平性 | 非公平锁 | 可选公平/非公平 |
||| 条件变量 | 只有一个 wait/notify | 多个 Condition 精准唤醒 |
||| 锁状态 | 无法判断是否获取到锁 | tryLock() 可判断 |
||| 释放锁 | 异常时自动释放 | 必须在 finally 中手动释放 |
一句话记住:synchronized 是自动挡,Lock 是手动挡——自动挡省心,手动挡灵活。
synchronized:自动挡,省心但死板
synchronized用起来特别简单,加个关键字就完事:
synchronized(obj){// 进入自动加锁,退出自动释放 👈balance-=100;}你不需要手动释放锁,哪怕代码抛异常,JVM 也会保证锁被释放。这就是"自动挡"的好处——省心。
但省心的代价是不灵活。你想中途放弃等锁?不行。你想设个超时?不行。你想精准唤醒某个等待线程?只能notifyAll()一把梭,把所有等着的线程都叫醒。
生活类比:synchronized就像自动门,人到门前自动开,人走自动关,但你没法控制它什么时候开、开多久。
Lock:手动挡,灵活但要自己负责
ReentrantLock是Lock接口最常用的实现类,用法稍微麻烦一点:
Locklock=newReentrantLock();lock.lock();// 手动加锁 👈try{balance-=100;}finally{lock.unlock();// 必须手动释放 👈 忘了就死锁!}重点:unlock()必须放在finally块里。忘了释放锁,别的线程就永远等下去了。这是"手动挡"的代价——你得自己负责。
但换来的灵活性非常强大:
1. 超时获取锁——不死等
if(lock.tryLock(3,TimeUnit.SECONDS)){// 3秒拿不到就放弃 👈try{/* 业务 */}finally{lock.unlock();}}2. 可中断——等锁的时候能被打断
lock.lockInterruptibly();// 等锁期间可以被 interrupt() 打断 👈3. 公平锁——先来先得
LockfairLock=newReentrantLock(true);// true = 公平锁 👈synchronized只能是非公平锁,后来的线程可能"插队"先拿到锁。
Condition:Lock 的杀手锏
如果说tryLock和公平锁只是"锦上添花",那Condition就是 Lock 相比 synchronized 的杀手锏。
synchronized只有一把wait/notify,唤醒线程时没法指定是谁。但Lock可以创建多个Condition,精准唤醒:
Locklock=newReentrantLock();ConditionnotFull=lock.newCondition();// 队列没满条件 👈ConditionnotEmpty=lock.newCondition();// 队列没空条件 👈// 生产者:队列满了等"没满",放入后通知"没空"notFull.await();notEmpty.signal();// 精准唤醒消费者 👈// 消费者:队列空了等"没空",取出后通知"没满"notEmpty.await();notFull.signal();// 精准唤醒生产者 👈synchronized的notifyAll()是大喇叭广播,Condition的signal()是定向通知。生产者-消费者模型中,这个精准度差别太大了。
synchronized 的逆袭:JVM 层面优化
别以为synchronized就一无是处。JDK 6 之后,JVM 对synchronized做了大量优化:
- 偏向锁:只有一个线程访问时,连 CAS 都省了
- 轻量级锁:短时间竞争用 CAS 自旋,不阻塞
- 重量级锁:竞争激烈才膨胀为操作系统互斥量
这些优化都是 JVM 自动完成的,你代码不用改。所以在竞争不激烈的场景,synchronized的性能并不比Lock差。
synchronized vs Lock 全景
synchronized 与 Lock 对比 全景 核心差异 ├── synchronized ── JVM 关键字,隐式加锁释放 │ ├── 自动释放锁(异常安全) │ ├── 不可中断、不可超时 │ ├── 只能非公平锁 │ └── 单条件变量(wait/notify) └── Lock ── JDK API,显式加锁释放 ├── 必须手动释放(finally 中 unlock) ├── 可中断(lockInterruptibly) ├── 可超时(tryLock) ├── 可选公平/非公平 └── 多条件变量(Condition) 选择建议 ├── 简单场景 → synchronized(代码简洁,自动优化) ├── 需要超时/中断/公平 → ReentrantLock └── 需要精准唤醒 → Lock + Condition 口诀:sync 自动省心用,Lock 灵活手动控, 超时中断公平锁,Condition 精准唤。回答技巧与点评
标准回答
synchronized 是 JVM 层面的关键字,自动加锁释放锁,不可中断、不可超时、只能非公平;Lock 是 JDK 层面的 API 接口,需手动加锁释放,支持可中断、超时获取、公平锁选择,以及多个 Condition 精准唤醒。简单场景优先用 synchronized,需要灵活控制时用 Lock。
加分回答
- 设计哲学:synchronized 体现了"约定优于配置"的思想,用最简单的语法完成最常见的场景;Lock 体现了"显式优于隐式",把控制权交给开发者,适合复杂并发场景
- 性能边界:JDK 6 之后 synchronized 有偏向锁、轻量级锁等优化,低竞争场景性能不输 Lock。但高竞争场景下,Lock 的
tryLock能避免线程长时间阻塞,整体吞吐量更优 - 进阶应用:
ReentrantReadWriteLock是 Lock 的另一个重要实现,读写分离,适合读多写少场景。synchronized 没有读写分离的能力
面试官点评
这道题考的是你对两种锁机制的全面理解和场景选择能力。只说"synchronized 自动、Lock 手动"太浅了。能展开讲 Condition 的精准唤醒、JVM 锁优化、以及 tryLock 在死锁避免中的作用,才能拿高分。如果能顺带提到 ReadWriteLock,说明你的知识面足够宽。
原文阅读
内容有帮助?点赞、收藏、关注三连!评论区等你 💪
