真实面经题目 · 原创解析

java中的锁有哪些?

Java 中的锁不能只回答 synchronized 和 ReentrantLock,而要按实现机制、语义特征、等待方式、读写模型和 JVM 优化层级来拆解。面试时应从对象监视器、AQS 显式锁、读写锁、StampedLock、CAS 原子类、LockSupport 与 Condition 这些核心工具出发,再说明乐观/悲观、公平/非公平、可重入/不可重入、自旋/阻塞等分类维度,并结合适用场景、性能取舍和常见陷阱给出判断依据。

出现于:阿里巴巴 · 后端开发

60 秒回答模板

Java 中的锁可以从多个维度回答。第一类是内置锁,也就是 synchronized,它基于对象监视器实现,可以修饰实例方法、静态方法或代码块,进入临界区时自动加锁,退出或异常时自动释放,配合 wait、notify、notifyAll 完成线程协作。第二类是 JUC 中基于 AQS 的显式锁,典型代表是 ReentrantLock,它支持可重入、可中断获取锁、超时获取锁、公平锁选择以及多个 Condition 条件队列,适合需要更细控制的并发场景。第三类是读写分离锁,例如 ReentrantReadWriteLock,适用于读多写少,允许多个读线程并发,但写线程互斥。第四类是 StampedLock,它提供写锁、悲观读锁和乐观读模式,适合读多写少且读操作较短的场景,但它不可重入,使用不当容易造成死锁或校验遗漏。除此之外,还要从锁语义上区分悲观锁和乐观锁,悲观锁先互斥再访问,乐观锁先访问再通过 CAS 或版本号校验冲突;从调度上区分公平锁和非公平锁,公平锁降低饥饿风险但吞吐通常较低,非公平锁吞吐更好但可能让等待线程更久;从线程状态上区分自旋锁和阻塞锁,自旋适合临界区很短的竞争,阻塞适合等待时间较长的竞争。JVM 还曾对 synchronized 做过偏向锁、轻量级锁、重量级锁等优化概念,但需要注意偏向锁在较新 JDK 中已经默认禁用并移除,不能把它当作所有版本都稳定存在的运行时特性。实际选型时,简单互斥优先 synchronized,需要可中断、超时、多条件队列时选 ReentrantLock,读多写少可考虑 ReadWriteLock 或 StampedLock,简单状态更新优先考虑 Atomic 类和 CAS,但要注意 ABA、自旋开销、锁粒度过大、忘记释放锁以及条件队列误用等问题。

考点 内置锁 synchronized
主线 对象监视器与等待通知
易错点 只回答 synchronized、ReentrantL…

深入解析

01

内置锁 synchronized

synchronized 是 Java 语言级锁,锁对象可以是实例对象、Class 对象或显式传入的对象引用。它依赖对象监视器完成互斥,进入同步块时获取 monitor,退出同步块时释放 monitor,异常退出也会自动释放,因此写法简单、可靠性高。

02

对象监视器与等待通知

Object monitor 不只负责互斥,也承载 wait、notify、notifyAll 的线程协作语义。wait 必须在持有同一对象监视器时调用,会释放锁并进入等待队列;notify 只负责唤醒等待线程,线程真正继续执行还要重新竞争锁。

03

ReentrantLock 与 AQS

ReentrantLock 是显式锁,底层依赖 AQS 管理同步状态和等待队列。它相比 synchronized 提供更强控制能力,例如 lockInterruptibly、tryLock、超时获取锁、公平锁构造参数以及多个 Condition,适合复杂同步流程。

04

Condition 条件队列

Condition 是 ReentrantLock 的条件等待机制,一个锁可以创建多个条件队列,从而把不同等待原因拆开管理。await 会释放当前锁并挂起线程,signal 或 signalAll 唤醒后还要重新获取锁,通常必须配合 while 循环校验条件。

05

ReadWriteLock

ReadWriteLock 将访问拆成读锁和写锁,读读可以并发,读写和写写互斥。它适合读远多于写、读操作耗时明显、共享数据一致性要求高的场景;如果写很多或临界区很短,读写锁的管理成本可能抵消收益。

06

StampedLock

StampedLock 提供写锁、悲观读锁和乐观读模式。乐观读不是传统意义上的互斥锁,读取后必须通过 stamp 校验期间是否发生写入;它适合读多写少、读路径短、可以重试的场景,但不可重入,不能直接替代 ReentrantReadWriteLock。

07

乐观锁与悲观锁

悲观锁认为冲突经常发生,所以访问共享资源前先加互斥锁;乐观锁认为冲突较少,先执行操作,再通过 CAS、版本号或时间戳校验是否冲突。AtomicInteger、AtomicReference 等原子类就是典型 CAS 风格工具。

08

公平锁与非公平锁

公平锁倾向按照等待顺序分配锁,减少线程饥饿,但需要更多排队和调度成本;非公平锁允许新来的线程直接竞争,吞吐量通常更高。ReentrantLock 可以选择公平或非公平,synchronized 没有提供公平性开关。

09

可重入与不可重入

可重入锁允许同一线程在已经持有锁的情况下再次获取同一把锁,避免递归调用或链式调用时自我死锁。synchronized 和 ReentrantLock 都是可重入锁,而 StampedLock 不可重入,嵌套获取时需要格外谨慎。

10

自旋、阻塞与锁状态

自旋锁让线程短时间循环等待,避免立刻阻塞带来的上下文切换成本,适合锁持有时间极短的竞争;阻塞锁会挂起线程,适合等待较久的竞争。synchronized 的偏向锁、轻量级锁、重量级锁属于 JVM 优化和实现层概念,并且偏向锁在新版本 JDK 中已有重要变化。

易错点

  • 只回答 synchronized、ReentrantLock、ReadWriteLock,缺少按锁语义和等待方式进行分类。
  • 把 synchronized 说成一定性能差,忽略 JVM 对内置锁的长期优化。
  • 认为 ReentrantLock 会自动释放锁,忘记说明必须在 finally 中 unlock。
  • 把 CAS 说成没有锁、没有成本,忽略自旋失败、CPU 消耗和 ABA 问题。
  • 认为读写锁在所有读多场景都更快,忽略锁升级限制、写锁饥饿和管理开销。
  • 把 StampedLock 当成可重入锁使用,忽略不可重入和乐观读校验要求。
  • 把公平锁理解为性能一定更好,忽略排队和线程调度成本。
  • 把偏向锁、轻量级锁、重量级锁当作所有 JDK 版本都相同的固定结论。

面试官追问

synchronized 和 ReentrantLock 的区别是什么?

synchronized 是语言级内置锁,自动释放,写法简单;ReentrantLock 是显式锁,支持可中断、超时、公平锁和多个 Condition,但需要手动 unlock,适合更复杂控制。

AQS 的核心思想是什么?

AQS 用一个 state 表示同步状态,用 FIFO 队列管理竞争失败的线程,并提供独占和共享两种获取模式。具体锁只需要定义如何尝试获取和释放状态。

为什么 ReentrantLock 要在 finally 中 unlock?

因为 ReentrantLock 不会像 synchronized 一样自动释放。业务代码抛异常时如果没有在 finally 中 unlock,锁会一直被当前线程持有,其他线程可能永久阻塞。

ReadWriteLock 在什么情况下可能不升反降?

如果写操作频繁、读操作很短、锁竞争复杂或存在锁升级需求,读写锁的状态维护和排队成本可能超过收益,还可能带来写线程饥饿问题。

StampedLock 的乐观读为什么必须校验 stamp?

乐观读期间并没有真正阻止写线程修改数据,因此读取完成后必须 validate 校验期间是否发生写入。如果校验失败,就要退化为悲观读锁或重新读取。

CAS 为什么可能出现 ABA 问题?

CAS 只比较当前值是否等于旧值,如果值从 A 变成 B 又变回 A,CAS 可能误以为没有变化。常见解决方式是增加版本号或使用带版本戳的原子引用。

公平锁一定比非公平锁好吗?

不一定。公平锁能减少饥饿,但需要更严格排队和更多调度切换,吞吐通常较低。很多默认实现偏向非公平,是为了在大多数场景获得更高吞吐。