在Java虚拟机中,synchronized关键字的实现依赖对象头中的Mark Word结构,JVM为了降低锁操作的开销,设计了偏向锁、轻量级锁、重量级锁三种状态,并且会根据竞争情况自动完成锁升级,这也是synchronized性能优化的重要体现。

三种锁的基本概念
偏向锁
偏向锁是JVM锁升级的第一阶段,它的核心思想是:如果一个线程获得了锁,那么锁就进入偏向模式,后续该线程再次获取锁时不需要进行任何同步操作,连CAS操作都不需要执行,直接判断线程ID是否匹配即可。
偏向锁适合只有一个线程访问同步块的场景,默认是开启的,可以通过JVM参数-XX:-UseBiasedLocking关闭偏向锁,关闭后程序默认直接进入轻量级锁状态。
轻量级锁
当偏向锁被另一个线程尝试获取时,偏向锁就会升级为轻量级锁。轻量级锁的核心思想是:在多线程交替执行同步块的场景下,避免使用操作系统层面的互斥量,而是通过CAS操作尝试获取锁。
轻量级锁的获取过程会在线程栈中创建锁记录,将对象头的Mark Word复制到锁记录中,然后尝试用CAS将对象头的Mark Word替换为指向锁记录的指针,如果成功则获取锁,失败则说明存在竞争,会膨胀为重量级锁。
重量级锁
重量级锁是锁升级的最终状态,依赖操作系统的互斥量(Mutex)实现,线程获取不到锁时会进入阻塞状态,等待操作系统调度唤醒。重量级锁的开销最大,因为涉及用户态和内核态的切换。
当轻量级锁CAS竞争失败,或者自旋次数超过阈值时,轻量级锁就会膨胀为重量级锁,此时对象的Mark Word会指向重量级锁的指针,后续所有竞争锁的线程都会直接阻塞。
synchronized锁升级的完整过程
synchronized的锁升级是单向的,只能从低级别向高级别升级,不能降级,完整流程如下:
- 初始状态下,对象头中的Mark Word处于无锁状态。
- 当第一个线程访问同步块并获取锁时,JVM会将锁状态标记为偏向锁,将线程ID记录在Mark Word中。
- 如果后续该线程再次进入同步块,只需要判断Mark Word中的线程ID是否和当前线程一致,一致则直接进入,不需要任何同步操作。
- 如果有另一个线程尝试获取这个锁,偏向锁就会被撤销,升级为轻量级锁。
- 轻量级锁状态下,线程通过CAS操作尝试获取锁,成功则执行同步块,失败则自旋等待。
- 如果自旋次数超过JVM设定的阈值(默认是10次,或者自适应自旋),或者存在第三个线程竞争锁,轻量级锁就会膨胀为重量级锁。
- 重量级锁状态下,获取不到锁的线程会进入阻塞队列,等待锁释放后被唤醒重新竞争。
锁状态对应的Mark Word结构
不同锁状态下,对象头Mark Word的结构不同,我们可以通过下表直观了解:
| 锁状态 | 25bit | 4bit | 1bit(是否是偏向锁) | 2bit(锁标志位) |
|---|---|---|---|---|
| 无锁 | 对象的hashCode | 对象分代年龄 | 0 | 01 |
| 偏向锁 | 线程ID | 对象分代年龄 | 1 | 01 |
| 轻量级锁 | 指向栈中锁记录的指针 | 0 | 00 | |
| 重量级锁 | 指向重量级锁的指针 | 0 | 10 | |
代码示例验证锁升级
我们可以通过简单的代码观察synchronized的锁升级过程,以下代码模拟了多线程竞争的场景:
public class SynchronizedUpgradeTest {
public static void main(String[] args) throws InterruptedException {
// 创建一个普通对象
Object lock = new Object();
// 第一个线程获取偏向锁
new Thread(() -> {
synchronized (lock) {
System.out.println("线程1获取锁,当前应该是偏向锁状态");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
// 等待第一个线程启动
Thread.sleep(500);
// 第二个线程竞争锁,触发偏向锁升级为轻量级锁
new Thread(() -> {
synchronized (lock) {
System.out.println("线程2获取锁,此时偏向锁会升级为轻量级锁");
}
}).start();
// 等待前两个线程执行完成
Thread.sleep(3000);
// 多个线程同时竞争,触发轻量级锁升级为重量级锁
for (int i = 0; i < 5; i++) {
new Thread(() -> {
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + "获取锁,此时会升级为重量级锁");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
}
运行上述代码时,我们可以通过JVM工具观察锁状态的变化,当多个线程同时竞争锁时,最终锁会升级为重量级锁,此时线程会进入阻塞状态,性能开销会明显增大。
实际开发中的优化建议
- 如果同步块只会单线程访问,不需要额外处理,JVM会默认使用偏向锁优化。
- 如果同步块是多线程交替执行,没有激烈竞争,偏向锁和轻量级锁已经足够,不需要额外优化。
- 如果存在激烈的多线程竞争,synchronized会自动升级为重量级锁,此时可以考虑使用并发包下的Lock接口实现类,比如ReentrantLock,根据场景选择公平锁或非公平锁。
- 尽量不要在循环中使用synchronized,避免频繁获取释放锁带来的性能损耗。
synchronized偏向锁轻量级锁重量级锁JVM锁升级修改时间:2026-06-28 18:00:33