目录

一、锁升级的原因

1. JDK 1.6 之前的痛点

2. 优化的核心依据

二、核心基础:Mark Word 结构

三、偏向锁:无竞争场景的最优解

1. 核心思想

2. 实现原理

(1)首次获取偏向锁(对应你描述的核心流程)

(2)锁重入(核心优化:无 CAS 操作)

(3)偏向锁撤销

3. 适用场景

四、轻量级锁:自旋解决短时间竞争

1. 核心思想

(1)加锁过程

(2)重入、自旋与升级

(3)解锁过程

3. 偏向锁 vs 轻量级锁的 Lock Record 区别

4. 适用场景

五、重量级锁:Monitor 机制兜底

1. 核心思想

2. 实现原理

3. 优缺点

六、锁升级完整流程


在上一篇文章中,我们讲解了 synchronized 的基础使用和底层 Monitor 重量级锁原理,而 JDK 1.6 之后 synchronized 之所以性能大幅提升,核心原因就是引入了锁升级机制(偏向锁 → 轻量级锁 → 重量级锁)。本文将带你彻底搞懂这三种锁的实现原理、升级时机和核心区别。

一、锁升级的原因

1. JDK 1.6 之前的痛点

JDK 1.6 之前,synchronized 只有重量级锁一种形态,完全依赖操作系统的互斥量(Mutex)和 JVM 的 Monitor 实现。线程的阻塞 / 唤醒需要从用户态切换到内核态,每次切换开销约为几十纳秒到几微秒,而普通的方法调用仅需几纳秒 —— 这种 “杀鸡用牛刀” 的设计,让 synchronized 在低竞争场景下性能极差。

2. 优化的核心依据

JVM 团队通过大量实践观察到两个关键特征:

  • 偏向性:大多数场景下,锁只会被一个线程重复获取(无竞争);
  • 短暂性:即使有竞争,也只是多个线程短时间交替争抢,而非长时间占用。

基于这两个特征,JVM 设计了按需升级的锁策略:从开销最小的偏向锁开始,仅当竞争加剧时才升级为轻量级锁、最终到重量级锁,兼顾不同竞争场景的性能

二、核心基础:Mark Word 结构

锁升级的本质是修改对象头中 Mark Word(标记字段)的内容和标志位,理解 Mark Word 是掌握锁升级的前提。

  • hashcode:25位的对象标识Hash码
  • age:对象分代年龄占4位
  • biased_lock:偏向锁标识,占1位 ,0表示没有开始偏向锁,1表示开启了偏向锁
  • thread:持有偏向锁的线程ID,占23位
  • epoch:偏向时间戳,占2位
  • ptr_to_lock_record:轻量级锁状态下,指向栈中锁记录的指针,占30位
  • ptr_to_heavyweight_monitor:重量级锁状态下,指向对象监视器Monitor的指针,占30位

以 64 位 JVM 为例,Mark Word 结构如下(核心字段):

锁状态 64 位 Mark Word 存储内容 标志位(最后 2 位) 偏向锁标志(倒数 3 位)
无锁 对象哈希码(25bit) + 分代年龄(4bit) + 未使用(1bit) + 无锁标志 01 0
偏向锁 偏向线程 ID(54bit) + epoch(2bit) + 分代年龄(4bit) + 偏向锁标志 01 1
轻量级锁 指向栈中锁记录(Lock Record)的指针(62bit) + 轻量级锁标志 00 -
重量级锁 指向 Monitor 对象的指针(62bit) + 重量级锁标志 10 -
GC 标记 空 + GC 标记位 11 -

关键说明

  • 所有锁状态的切换,都是通过 CAS 操作修改 Mark Word 的内容和标志位实现;
  • 偏向锁和无锁的标志位都是 01,通过 “偏向锁标志位” 区分;
  • 轻量级 / 重量级锁的核心是 Mark Word 存储 “指针”(指向栈中锁记录或 Monitor)。

关键:所有锁状态的切换,本质都是修改 Mark Word 的标志位和存储内容,无需创建新对象,开销极小。

三、偏向锁:无竞争场景的最优解

1. 核心思想

偏向锁的目标是:在只有一个线程获取锁的场景下,消除锁的获取 / 释放开销

“偏向” 就是指锁会偏向于第一个获取它的线程,只要后续没有其他线程竞争,该线程就永远不需要再进行加锁 / 解锁操作。

2. 实现原理

(1)首次获取偏向锁(对应你描述的核心流程)

1.创建 Lock Record:线程进入同步代码块时,JVM 会在当前线程的栈帧中创建一个 Lock Record(锁记录) 结构体,将其 obj 字段指向当前锁对象。

2.CAS 抢占偏向锁

  • 线程通过 CAS 指令,将自身线程 ID 存储到锁对象的 Mark Word 中;
  • 同时将 Mark Word 的最后 3 位标志位设置为 101(偏向锁标识);
  • 若对象处于无锁状态(标志位 001),CAS 修改成功,该线程获得偏向锁。

3.关联 Lock Record 与锁对象:完成 Lock Record 和锁对象的绑定,线程开始执行同步代码。

(2)锁重入(核心优化:无 CAS 操作)

线程再次进入同步代码块(锁重入)时:

1.JVM 在栈帧中创建一个新的 Lock Record;

2.轻量级检查:仅判断锁对象 Mark Word 中的线程 ID 是否为当前线程 ID,无需执行 CAS 操作;

3.标记重入计数:将新创建的 Lock Record 的核心字段设为 null,以此作为 “重入计数器”—— 栈帧中有多少个 null 的 Lock Record,就代表锁重入了多少次;

 相比轻量级锁 “每次重入都要 CAS”,此设计完全消除了重入的性能开销。

(3)偏向锁撤销

当有第二个线程尝试获取锁时,偏向锁会被撤销(需等待全局安全点):

1.JVM 暂停持有偏向锁的线程,遍历其栈帧中的所有 Lock Record,清空与当前锁对象绑定的 Lock Record;

2.撤销后有两种结果:

若原持有线程已退出同步代码:将锁恢复为无锁状态;

若原持有线程仍在执行:升级为轻量级锁,原线程的 Lock Record 会填充锁对象的 Mark Word 副本。

3. 适用场景

  • 单线程重复获取同一把锁的场景(如单线程循环调用同步方法)。
  • 无任何线程竞争,性能开销几乎为 0。

四、轻量级锁:自旋解决短时间竞争

1. 核心思想

轻量级锁适用于多个线程交替获取锁(短时间竞争)的场景,通过自旋(用户态循环)代替线程阻塞,避免内核态切换的开销。

“轻量级” 体现在:无需调用操作系统的互斥量,完全在用户态完成锁的获取 / 释放,而 Lock Record 是轻量级锁的核心载体。

(1)加锁过程

        1.创建 Lock Record:线程进入同步代码块时,JVM 会在该线程的栈帧中创建一个 Lock Record,核心字段包括:

  • obj:指向锁对象的引用;
  • displaced_mark_word:存储锁对象当前 Mark Word 的副本(区别于偏向锁的空字段)。

注意注意:存储锁对象当前 Mark Word 的副本有什么作用?

其实就是为了解锁时需要把对象头恢复成原来的样子,副本就是 “原来的样子” 的唯一凭证

相当于是恢复现场

        2.CAS 替换 Mark Word:线程通过 CAS 操作,将锁对象的 Mark Word 替换为指向栈中 Lock Record 的指针

  • CAS 成功:轻量级锁加锁成功,Mark Word 标志位改为 00。
  • CAS 失败:说明有其他线程竞争,进入自旋。
(2)重入、自旋与升级
  • 如果是当前线程已经持有该锁了,代表这是一次锁重入。设置Lock Record第一部分为null,起到了一个重入计数器的作用。

  • 自旋:如果锁已经被持有,其他线程不会立即阻塞,而是循环尝试获取锁(默认自旋 10 次,或自适应自旋)。
  • 升级时机
    • 自旋成功:获取锁,执行同步代码。
    • 自旋失败(超过自旋次数 / 其他线程已获取锁):轻量级锁升级为重量级锁。
(3)解锁过程

线程执行完同步代码后,通过 CAS 将 Lock Record 中备份的 displaced_mark_word 恢复到锁对象的 Mark Word 中:

  • CAS 成功:解锁完成,对象回到无锁状态;
  • CAS 失败:说明锁已升级为重量级锁,需触发 Monitor 机制唤醒阻塞线程。

3. 偏向锁 vs 轻量级锁的 Lock Record 区别

维度 偏向锁的 Lock Record 轻量级锁的 Lock Record
核心字段 obj 指向锁对象,重入时字段为 null obj 指向锁对象,存储 Mark Word 副本
核心操作 仅首次 CAS,重入无 CAS 加锁 / 重入均需 CAS 操作
性能开销 几乎为 0 低(用户态 CAS + 自旋)

4. 适用场景

  • 多线程短时间交替竞争锁(如循环次数少的同步代码)。
  • 自旋能减少线程阻塞的开销,但若自旋过久,反而会浪费 CPU 资源。

五、重量级锁:Monitor 机制兜底

1. 核心思想

当锁竞争激烈(多个线程长时间竞争)时,轻量级锁升级为重量级锁,依赖操作系统的 Mutex 和 JVM 的 Monitor 机制实现互斥。

2. 实现原理

  • 轻量级锁升级为重量级锁时,JVM 会为对象创建一个 Monitor(监视器),并将 Mark Word 改为指向 Monitor 的指针(标志位 10);
  • 竞争失败的线程不再自旋,而是进入 Monitor 的 _EntryList 队列阻塞(切换到内核态);
  • 持有锁的线程释放锁时,唤醒 _EntryList 中的线程重新竞争锁;
  • 关键:重量级锁阶段,线程栈中的 Lock Record 不再起核心作用,锁的管理完全交给 Monitor。

3. 优缺点

  • 缺点:线程阻塞 / 唤醒需要切换内核态,开销大。
  • 优点:适合长时间竞争的场景,避免自旋浪费 CPU 资源。

这里其实就是我上一篇文章所写的

六、锁升级完整流程

  1. 初始状态:对象为无锁状态,Mark Word 存储哈希码 + 分代年龄 + 001 标志位。
  2. 偏向锁:第一个线程创建 Lock Record,通过 CAS 将线程 ID 写入 Mark Word(标志位 101),重入时仅创建 null 的 Lock Record(无 CAS)。
  3. 轻量级锁:第二个线程竞争导致偏向锁撤销,升级为轻量级锁,线程创建存储 Mark Word 副本的 Lock Record,通过 CAS + 自旋竞争锁。
  4. 重量级锁:自旋失败 / 竞争加剧,升级为重量级锁,Lock Record 失效,依赖 Monitor 机制实现互斥。

关键:锁升级是单向的(只能从低到高),不会降级(如重量级锁不会变回轻量级锁),避免频繁切换锁状态带来的开销。

感兴趣的宝子可以关注一波,后续会更新更多有用的知识!!!

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐