高 基础
synchronized与Lock区别#
一句话答案#
synchronized 是关键字自动释放锁,Lock 是接口需手动释放(finally),Lock 额外支持公平/可中断/超时/多条件。
核心要点
synchronized vs Lock 对比:
| 维度 | synchronized | Lock(以 ReentrantLock 为例) |
|---|---|---|
| 形式 | 关键字,JVM 实现(ObjectMonitor) | 接口,Java 实现(AQS) |
| 释放 | 自动(退出块/异常时 JVM 释放) | 手动 unlock(),必须写在 finally |
| 可中断 / 超时 | 不支持 | lockInterruptibly() / tryLock(timeout) |
| 公平性 | 非公平 | 可选公平 |
| 等待队列 | 一个(wait/notify) | 多个 Condition |
| 读写分离 | 不支持 | ReentrantReadWriteLock / StampedLock |
| 虚拟线程 | JDK 21–23 阻塞时会 pin 载体线程;JDK 24(JEP 491)起不再 pin | 不 pin |
synchronized 侧底层:ObjectMonitor 的核心结构(HotSpot,简化):
ObjectMonitor {
_owner → 指向持有锁的线程
_recursions = 0 // 重入计数
_cxq / _EntryList → 竞争锁失败的线程队列(阻塞状态,新竞争者先进 _cxq)
_WaitSet → 调用 wait() 后等待唤醒的线程队列
}cpp获锁流程:
线程尝试获锁
→ CAS 将 _owner 设为当前线程
→ 成功:进入同步块(已是 owner 则 _recursions++)
→ 失败(已有线程持有):自适应自旋几次,仍失败则进入 _cxq/_EntryList 阻塞等待
持锁线程调用 wait():
→ 保存重入次数后完全释放锁(_owner = null)
→ 当前线程移入 _WaitSet
notify():
→ 将 _WaitSet 中的一个线程移到 _EntryList/_cxq,等待重新竞争锁
释放锁:
→ _recursions > 0 时:_recursions--
→ _recursions = 0 时:_owner = null,唤醒 _EntryList/_cxq 中的线程竞争plaintext可重入的实现(_recursions 计数器):
- 同一线程再次获取同一把锁:检查
_owner == 当前线程→_recursions++(重入计数) - 每次
monitorexit(退出 synchronized 块):_recursions-- - 只有
_recursions == 0时再退出才真正释放锁(_owner = null) - 如果不支持重入,持锁线程调用自身的
synchronized方法会立刻死锁
面试回答(2分钟版)
synchronized 和 Lock 都能实现线程同步,但在机制和灵活性上有明显区别。synchronized 是 Java 关键字,由 JVM 在字节码层面通过 monitorenter 和 monitorexit 指令实现,最大的优点是自动释放锁,退出同步块或抛异常时 JVM 保证锁一定释放。Lock 是接口,典型实现是 ReentrantLock,需要在 finally 中手动调用 unlock 释放锁,忘记释放就会死锁。Lock 的优势在于额外能力:支持公平锁、可中断的获取锁、带超时的 tryLock 防死锁、以及通过多个 Condition 实现精细的等待通知机制。底层实现上,synchronized 依赖 ObjectMonitor 的 owner、EntryList 和 WaitSet 三个核心结构,Lock 则基于 AQS 框架。JDK 6 之后 synchronized 做了大量优化,包括偏向锁、轻量级锁、锁消除等,性能和 Lock 已经非常接近。选择原则是:简单场景优先用 synchronized,需要超时、中断、公平或多条件时用 Lock。
追问与易错
追问方向:
- “tryLock 有什么用?举个场景?”→ 尝试获锁失败立即返回 false 不阻塞,支持超时;典型场景是避免死锁——两个资源加锁时 tryLock 失败就释放已持有的锁重试
- “Condition 对比 wait/notify 优势?”→ 一个 Lock 可以创建多个 Condition 实现精确唤醒,而 synchronized 只有一个等待队列;如 ArrayBlockingQueue 用 notEmpty 和 notFull 两个 Condition 分别唤醒消费者和生产者
- “什么时候必须用 Lock?”→ 需要超时获取、可中断等待、公平锁、多条件变量、读写分离锁时必须用 Lock,synchronized 不支持这些特性
易错点:
- ❌ Lock 比 synchronized 快——JDK6 后差距很小
- ❌ 忘记 finally 释放 Lock