要理解 Java 中的偏向锁、轻量级锁、重量级锁,首先要明确:这三种锁是 synchronized 关键字的锁升级机制(JDK 1.6 引入,目的是降低锁的开销),核心是根据竞争程度动态调整锁的形态,从无竞争到高竞争,锁会逐步从偏向锁 → 轻量级锁 → 重量级锁升级(不可逆)。

一、核心背景:为什么需要锁升级?

在 JDK 1.6 之前,synchronized 是纯粹的重量级锁—— 依赖操作系统的互斥量(Mutex)实现,每次加锁 / 解锁都需要切换内核态(用户态 ↔ 内核态),开销极大。
但实际开发中,大部分场景下 synchronized 锁定的代码块竞争非常小(甚至只有一个线程执行),因此 JVM 针对不同竞争程度设计了分级锁:

  • 无竞争 → 偏向锁(几乎无开销);
  • 轻度竞争 → 轻量级锁(自旋锁,避免内核态切换);
  • 重度竞争 → 重量级锁(操作系统互斥量,保证线程安全)。

二、三种锁的详细解析

1. 偏向锁(Biased Locking)
核心特点
  • 适用场景:无竞争(只有一个线程反复获取同一把锁);
  • 核心思想:锁会 “偏向” 第一个获取它的线程,后续该线程再次获取锁时,无需任何同步操作(连 CAS 都不用),直接执行;
  • 实现原理:
    • 线程第一次获取偏向锁时,会在对象头的 “Mark Word” 中记录自己的线程 ID;
    • 后续该线程进入 / 退出同步块时,只需检查 Mark Word 中的线程 ID 是否为自己:
      • 是 → 直接进入(无任何开销);
      • 否 → 触发锁升级。
public class BiasedLockDemo {
    private static final Object lock = new Object();

    public static void main(String[] args) {
        // 单线程反复获取锁,触发偏向锁
        for (int i = 0; i < 10000; i++) {
            synchronized (lock) {
                // 无其他线程竞争,始终是偏向锁
                System.out.println("当前线程:" + Thread.currentThread().getName());
            }
        }
    }
}
关键细节
  • 偏向锁默认是延迟启动的(JVM 启动后约 4 秒),可通过 JVM 参数 -XX:-UseBiasedLocking 关闭偏向锁;
  • 偏向锁的释放不需要主动操作,只有当其他线程尝试获取锁时,才会撤销偏向锁(触发锁升级)。
2. 轻量级锁(Lightweight Locking)
核心特点
  • 适用场景:轻度竞争(多个线程交替获取锁,无长时间阻塞);
  • 核心思想:用自旋锁(CAS 操作) 替代操作系统互斥量,避免内核态切换;
  • 实现原理:
    • 线程获取轻量级锁时,会在自己的栈帧中创建 “锁记录”(Lock Record),并将对象头的 Mark Word 复制到锁记录中;
    • 通过 CAS 操作将对象头的 Mark Word 替换为指向锁记录的指针:
      • CAS 成功 → 获取锁,执行同步代码;
      • CAS 失败 → 线程自旋(循环重试 CAS),自旋次数达到阈值后,升级为重量级锁。
关键细节
  • 自旋锁的自旋次数:JDK 1.6 后默认自适应(根据前一次自旋的成功率调整),也可通过 -XX:PreBlockSpin 指定固定次数;
  • 轻量级锁的释放:通过 CAS 将对象头的 Mark Word 恢复为原始值,成功则释放锁,失败则说明锁已升级为重量级锁。

3. 重量级锁(Heavyweight Locking)

核心特点
  • 适用场景:重度竞争(多个线程同时争抢锁,自旋无法获取);
  • 核心思想:依赖操作系统的互斥量(Mutex) 实现,线程获取不到锁时会进入阻塞状态(放弃 CPU);
  • 实现原理:
    • 锁升级为重量级锁后,对象头的 Mark Word 会指向一个 “监视器(Monitor)” 对象;
    • Monitor 维护了一个等待队列,未获取到锁的线程会被放入队列并阻塞;
    • 持有锁的线程释放锁时,会唤醒队列中的线程,竞争锁。
关键细节
  • 重量级锁的最大开销:线程阻塞 / 唤醒需要切换内核态(用户态 ↔ 内核态),这是一个非常耗时的操作;
  • 重量级锁是 synchronized 的 “最终形态”,一旦升级,不会再降级为轻量级锁 / 偏向锁。

三、锁升级的完整流程

用流程图直观展示锁的升级过程:
第一个线程获取锁
在这里插入图片描述

四、三种锁的核心对比

锁类型 适用场景 实现方式 性能开销 线程状态
偏向锁 无竞争(单线程) 记录线程 ID ,无同步操作 几乎无开销
轻量级锁 轻度竞争(交替获取) 自旋 + CAS 操作 低开销(用户态) 运行态(自旋,不阻塞)
重量级锁 重度竞争(同时争抢) 操作系统 Mutex+Monitor 队列 高开销(内核态) 阻塞态(放弃 CPU)

五、实战调优建议

偏向锁调优:
  • 适合单线程频繁操作同步块的场景(如单线程循环加锁);
  • 如果程序中多线程竞争激烈,可通过 -XX:-UseBiasedLocking 关闭偏向锁(避免偏向锁撤销的开销)。
轻量级锁调优:
  • 适合短时间、低竞争的同步块(自旋能快速获取锁);
  • 避免同步块执行时间过长(自旋会浪费 CPU 资源)。
重量级锁规避:
  • 尽量缩小同步块的范围(仅包裹核心并发逻辑);
  • 高并发场景下,可考虑用 java.util.concurrent 包的锁(如 ReentrantLock)替代 synchronized,支持更灵活的锁策略。

总结

  • 核心逻辑:synchronized 的锁升级是 “按需升级”,从无竞争到高竞争,锁的开销逐步增大,兼顾了低竞争下的性能和高竞争下的安全性;
  • 性能优先级:偏向锁 > 轻量级锁 > 重量级锁;
  • 使用原则:根据业务的竞争程度选择(或让 JVM 自动适配),低竞争用偏向 / 轻量级锁,高竞争需优化代码减少锁争抢。
Logo

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

更多推荐