一、什么是偏向锁?
偏向锁(Biased Locking)是 Java 6 引入的一项锁优化技术,其核心思想是:在无竞争的情况下,消除整个同步原语的开销。
简单比喻:一把锁默认“偏袒”第一个访问它的线程。只要这个线程后续再来,无需任何原子操作(如 CAS)即可直接进入同步块,就像回自己家一样方便。
二、为什么需要偏向锁?
在早期 synchronized 实现中,即使没有线程竞争,每次加锁/解锁也需要执行 CAS 操作更新对象头中的锁标志,这在高频单线程访问同步块的场景下(如某些工具类、缓存)会产生不必要的性能损耗。
偏向锁的目标:将“锁属于某个线程”这个状态记录在对象头里,让该线程后续访问时实现“零成本”加锁。
三、偏向锁的工作原理(三步曲)
1. 加锁流程
- 步骤一(检查):线程访问同步块时,先检查对象头中的偏向锁标志和线程ID。
- 如果标志为1且线程ID指向自己 → 直接进入同步块(零成本)。
- 如果标志为1但线程ID是别人 → 进入锁升级流程。
- 如果标志为0(无锁状态)→ 尝试通过 CAS 将对象头中的线程ID设为当前线程ID,并设置偏向模式。
- 步骤二(撤销):当有第二个线程尝试获取锁时,JVM 需要撤销偏向锁。这个过程需要暂停持有偏向锁的线程(Stop The World),检查其是否仍在同步块中。
- 若已退出 → 将对象头恢复为无锁状态,然后两个线程竞争升级为轻量级锁。
- 若未退出 → 直接升级为轻量级锁。
- 步骤三(升级):撤销后,锁状态变为轻量级锁,后续竞争通过 CAS 自旋进行。
2. 对象头变化(64位虚拟机为例)
无锁状态: Mark Word (64 bits): [unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:0 | lock:01] 偏向锁状态(线程A持有): Mark Word: [thread_id:54 | epoch:2 | unused:1 | age:4 | biased_lock:1 | lock:01] 轻量级锁状态: Mark Word: [ptr_to_lock_record:62 | lock:00] 重量级锁状态: Mark Word: [ptr_to_monitor:62 | lock:10]四、偏向锁的“尴尬”与优化演变
1. 偏向锁的代价
- 撤销成本高:需要 STW,如果程序中存在大量锁竞争,频繁撤销反而降低性能。
- 适用场景窄:真正适合“一直只有一个线程访问”的场景并不多。
2. JDK 15 及之后的变革
- JDK 15 开始默认禁用偏向锁(
-XX:-UseBiasedLocking)。 - JDK 18 正式废弃偏向锁相关代码。
- 原因:现代硬件上 CAS 操作已非常高效,而偏向锁的撤销成本与其收益相比不再划算。维护其复杂性的性价比变低。
五、高频面试题与回答要点
面试题1:说说你对偏向锁的理解?
回答要点:
- 定义:一种消除无竞争情况下同步原语的优化。
- 核心:“偏袒”第一个获得锁的线程,记录其ID,该线程后续访问无需原子操作。
- 对象头:通过 Mark Word 中的偏向锁标志和线程ID实现。
- 生命周期:加锁(偏向)→ 出现竞争 → 撤销(可能STW)→ 升级为轻量级锁。
- 现状:JDK 15+ 默认禁用,JDK 18 废弃,因撤销成本高且适用场景少。
面试题2:偏向锁、轻量级锁、重量级锁有什么区别?
回答要点:
synchronized 锁的四种状态(锁升级过程)是:无锁 → 偏向锁 → 轻量级锁 → 重量级锁。它们的主要区别如下:
| 锁类型 | 适用场景 | 实现机制 | 开销 | 升级条件 |
|---|---|---|---|---|
| 无锁 | 无竞争,或使用 CAS 等乐观锁 | 对象头 Mark Word 记录 hashcode、分代年龄等 | 无同步开销 | 线程尝试获取锁时进入偏向锁或轻量级锁 |
| 偏向锁 | 只有一个线程访问同步块 | 对象头记录线程ID,该线程后续访问无需 CAS | 无竞争时近乎零开销 | 第二个线程尝试获取锁 |
| 轻量级锁 | 线程交替执行,竞争轻微 | CAS 自旋,栈中创建 Lock Record 拷贝对象头 | 少量 CAS 自旋开销 | 自旋失败或第三个线程竞争 |
| 重量级锁 | 多线程激烈竞争 | 操作系统互斥量(mutex),线程进入阻塞队列 | 上下文切换开销大 | 轻量级锁自旋失败 |
补充解释:
- 无锁:对象刚创建时的默认状态,或通过 CAS 实现的无锁编程。此时 Mark Word 存储对象的哈希码、分代年龄等信息。
- 偏向锁:在“只有一个线程使用锁”的场景下,JVM 通过 CAS 将线程 ID 记录到对象头,该线程后续进入同步块无需任何原子操作,实现“零成本”加锁。
- 轻量级锁:当有轻微竞争(线程交替执行)时,JVM 会在当前线程栈帧中创建 Lock Record,并尝试通过 CAS 将对象头指向该记录。如果成功,则获得锁;如果失败,则自旋重试。
- 重量级锁:当竞争激烈(自旋失败)时,锁升级为重量级锁。此时对象头指向操作系统级别的互斥量(monitor),未获得锁的线程会进入阻塞队列,由操作系统进行调度,涉及用户态到内核态的切换,开销最大。
锁升级方向:无锁 → 偏向锁 → 轻量级锁 → 重量级锁(不可逆)。JDK 6 之后默认开启锁升级优化,目的是在保证线程安全的前提下,尽量减少同步开销。
| 锁类型 | 适用场景 | 实现机制 | 开销 | 升级条件 |
|---|---|---|---|---|
| 偏向锁 | 只有一个线程访问同步块 | 对象头记录线程ID | 无竞争时近乎零开销 | 第二个线程尝试获取 |
| 轻量级锁 | 线程交替执行,竞争轻微 | CAS自旋,栈中Lock Record | 少量CAS自旋开销 | 自旋失败或第三个线程竞争 |
| 重量级锁 | 多线程激烈竞争 | 操作系统互斥量,线程阻塞排队 | 上下文切换开销大 | 轻量级锁自旋失败 |
面试题3:偏向锁为什么会被废弃?
标准答案:
- 撤销成本高:需要 STW,影响吞吐量。
- 收益下降:现代 CPU 的 CAS 操作很快,无竞争下的性能优势不再明显。
- 复杂度高:维护偏向锁状态机增加了 JVM 的复杂性。
- 实际场景少:大多数应用锁竞争频繁,偏向锁反而成为负担。
面试题4:如何手动开启/关闭偏向锁?
# JDK 15 之前 -XX:+UseBiasedLocking # 开启(默认) -XX:-UseBiasedLocking # 关闭 JDK 15 及之后 -XX:+UseBiasedLocking # 需显式开启,但已不推荐六、总结
- 偏向锁是什么:一项针对“单线程重复访问同步块”的优化,通过记录线程ID实现零成本加锁。
- 核心价值:在特定场景下消除同步开销。
- 主要问题:撤销需要 STW,竞争频繁时性能反而下降。
- 历史地位:是 JVM 锁优化演进中的重要一环,但其设计假设(锁多数时间不竞争)与现代高并发应用不符,故被废弃。
- 学习意义:理解偏向锁有助于掌握 JVM 同步优化的设计思想与权衡之道,是面试中考察对底层机制理解深度的经典题目。