两种锁分别是什么
synchronized 是 Java 关键字,通过 JVM Monitor 实现同步,可修饰方法和代码块,锁的获取与释放由 JVM 管理。ReentrantLock 是 JUC 提供的可重入锁,基于 AQS 实现,需要显式调用 lock() 与 unlock()。
synchronized 在字节码与 Monitor 层怎样工作?
同步代码块通常通过 monitorenter 和 monitorexit 指令表达,编译器还会安排异常退出时的释放路径。同步方法通常通过 ACC_SYNCHRONIZED 方法标志表达,由 JVM 在调用和退出时管理同步,而不是在方法体中照搬同样的指令序列。
监视器语义可以用三个概念理解:当前持有者、重入计数、等待协调。同一线程再次进入同一个监视器可以重入,退出时逐步减少持有次数;完全释放后其他线程才有机会获取。调用 wait 会释放该监视器并进入等待,返回前重新获取;notify 只是通知等待线程,不立即交出锁。
线程竞争同一监视器 ├─ 可以获取:进入临界区,或由持有者重入 └─ 不能获取:按 JVM 策略竞争或等待临界区退出/异常退出 → 释放持有 → 其他线程再竞争这里描述的是同步语义。HotSpot 可以使用快速路径、轻量级锁、自旋等优化,并不代表每次同步调用都创建或进入一个重量级 Monitor,也不需要第一轮背内部队列字段和对象头位数。
核心差异
| 维度 | synchronized | ReentrantLock |
|---|---|---|
| 形态 | 关键字 | 类 |
| 释放方式 | JVM 自动释放 | 必须手动释放 |
| 公平性 | 非公平 | 默认非公平,也可配置公平 |
| 扩展能力 | 基础同步 | 支持中断、超时获取等能力 |
| 底层机制 | JVM Monitor | AQS |
JDK 6 之后,synchronized 经历了多项锁优化;两者没有简单的“谁一定更快”。只有确实需要公平锁、可中断获取或超时等能力时,再选择 ReentrantLock。
Condition 与锁优化需要掌握什么?
ReentrantLock 可以创建多个 Condition,分别组织不同条件的等待和通知。await/signal 等操作要持有对应锁,等待条件同样应在循环中检查;await 会释放并在返回前重新获取对应锁。
synchronized 的实现存在多种优化,常见面试会提到自旋、轻量级锁等;具体策略随 JDK 演进,不能把“无锁 → 偏向 → 轻量 → 重量”当作现代所有 JDK 必经的固定路径。第一轮重点放在锁对象、互斥、可见性和释放责任。
两个线程按相反顺序获取多把锁可能发生死锁。减少嵌套、统一锁顺序、必要时超时获取可降低风险。锁范围应覆盖不变量,但不要无必要地把慢网络调用放进长时间临界区。
常见追问
公平锁为什么不是默认选择?
公平锁按等待顺序获取锁;非公平锁允许新线程先 CAS 尝试抢锁,失败才排队。非公平锁减少了唤醒队首线程和上下文切换的开销,因此通常吞吐量更高。
忘记 unlock 会怎样?
锁不会释放,其他线程会持续阻塞。ReentrantLock 应放在 try-finally 中释放:
lock.lock();try { // 临界区} finally { lock.unlock();}什么是可重入?
同一线程已持有一把锁时,可以再次获取它而不发生死锁。锁内部记录持有线程和重入次数;每次获取递增、每次释放递减,计数归零后才真正释放。