Java 锁升级机制详解

Java 锁升级机制详解

引言

最近有个三年左右的兄弟面试java 被问到这样一道经典的八股文面试题: 你讲讲java里面的锁升级? 他感觉回答的不是很好,然后回去找资料学习了一波,然后下面是他输出的文章,希望对找工作的其他朋友也有些帮助。

1. 概述

Java 的锁升级机制是 JVM 在 JDK 1.6 后引入的重要优化策略,目的是在多线程环境下平衡 线程安全性能开销。通过动态调整锁的复杂度,JVM 根据竞争强度逐步升级锁的状态,避免在低竞争场景下使用高成本的重量级锁。

2. 锁类型及特点

锁类型 适用场景 性能开销 核心机制
无锁(Unlocked) 无线程竞争 极低 直接通过 CAS 操作尝试获取锁。
偏向锁(Biased Locking) 单线程重复访问(无竞争) 极低 对象头记录偏向线程 ID,后续同一线程无需竞争,直接获取锁。
轻量级锁(Lightweight Lock) 低竞争(多个线程交替访问) 中等 通过 CAS 自旋尝试获取锁,避免操作系统级别的阻塞。
重量级锁(Heavyweight Lock) 高竞争(长时间阻塞或高并发) 依赖操作系统互斥量(Mutex),线程被挂起并排队等待。

3. 锁升级的过程

锁升级路径为:无锁 → 偏向锁 → 轻量级锁 → 重量级锁 ,且 不可逆(只能升级,不能降级)。

3.1 无锁 → 偏向锁

  • 触发条件:第一个线程访问同步代码块时。
  • 过程
    1. JVM 通过 CAS 操作将对象头的 Mark Word 标记为偏向锁。
    2. 记录当前线程 ID 和偏向时间戳。
    3. 后续同一线程再次访问时,直接通过比对线程 ID 获取锁(无需 CAS 操作)。

3.2 偏向锁 → 轻量级锁

  • 触发条件:第二个线程尝试获取同一锁(出现竞争)。
  • 过程
    1. 偏向锁失效,JVM 撤销偏向锁(可能触发 STW,Stop-The-World)。
    2. 线程通过自旋(Spin)和 CAS 操作尝试获取锁。
    3. 若自旋成功,则升级为轻量级锁;否则继续自旋或升级为重量级锁。

3.3 轻量级锁 → 重量级锁

  • 触发条件
    • 自旋次数超过阈值(默认 10 次,可通过 -XX:PreBlockSpin 调整)。
    • 多个线程同时竞争锁(如第三个线程加入竞争)。
  • 过程
    1. JVM 将锁升级为重量级锁,对象头指向监视器(Monitor)。
    2. 线程进入操作系统内核态的阻塞队列,等待调度器唤醒。
    3. 未获取锁的线程通过 ObjectMonitor 等待唤醒。

4. 锁升级的优缺点

4.1 优点

  1. 减少无竞争场景的开销:偏向锁和轻量级锁避免了频繁的 CAS 和上下文切换。
  2. 动态适配竞争强度:在低竞争时保持高性能,在高竞争时保证线程安全。

4.2 缺点

  1. 偏向锁撤销开销:当其他线程竞争时,撤销偏向锁会导致 STW,影响性能。
  2. 重量级锁的高开销:在高竞争场景下,频繁的线程阻塞/唤醒会显著降低性能。

5. 锁升级的优化策略

5.1 减少锁持有时间

  • 优化方向:缩短同步代码块的执行时间,降低锁的竞争概率。

  • 示例

    java 复制代码
    // 不推荐:锁持有时间过长
    synchronized (lock) {
        // 复杂计算或 IO 操作
    }
    
    // 推荐:仅在关键代码块加锁
    int result = doSomeComputation(); // 非同步操作
    synchronized (lock) {
        sharedVariable = result;
    }

5.2 使用分段锁(Segment Locking)

  • 优化方向:将一个大锁拆分为多个小锁,减少锁的竞争范围。
  • 示例ConcurrentHashMap 使用分段锁(JDK 8 后改为 CAS + synchronized)。

5.3 禁用偏向锁

  • 适用场景:频繁切换线程的场景(如高并发服务)。

  • JVM 参数

    bash 复制代码
    -XX:-UseBiasedLocking  # 禁用偏向锁

5.4 调整自旋次数

  • 适用场景:轻量级锁的自旋可能因 CPU 空闲而浪费资源。

  • JVM 参数

    bash 复制代码
    -XX:PreBlockSpin=5  # 设置自旋次数为 5

6. 代码示例

java 复制代码
public class LockUpgradeExample {
    private final Object lock = new Object();

    public void performTask() {
        synchronized (lock) {
            // 同步代码块
        }
    }

    public static void main(String[] args) {
        LockUpgradeExample example = new LockUpgradeExample();
        Thread t1 = new Thread(example::performTask);
        Thread t2 = new Thread(example::performTask);

        t1.start(); // 初始为偏向锁(t1)
        t2.start(); // 触发偏向锁撤销,升级为轻量级锁
    }
}

7. 关键 JVM 参数

参数 作用
-XX:+UseBiasedLocking 开启/关闭偏向锁(默认开启,Java 15+ 默认关闭)。
-XX:BiasedLockingStartupDelay=0 立即启用偏向锁(避免延迟)。
-XX:PreBlockSpin 设置轻量级锁自旋次数(默认 10)。
-XX:-UseSpinning 关闭自旋锁(强制进入重量级锁)。

8. 总结

  • 锁升级是 JVM 自动管理的机制,开发者无需手动干预,但理解其原理有助于优化并发性能。
  • 偏向锁适合单线程场景,轻量级锁适合低竞争场景,重量级锁适合高竞争场景。
  • 锁升级不可逆,一旦升级到重量级锁,后续操作将始终使用重量级锁。

通过合理设计代码(如减少锁粒度、避免过早膨胀到重量级锁),可以最大化 Java 的并发性能。


相关推荐
盖世英雄酱581367 分钟前
🚀不改SQL,也能让SQL的执行效率提升100倍
java·数据库·后端
Java技术小馆17 分钟前
Cursor链接远程服务器实现项目部署
java
用户05956611920923 分钟前
深入理解Spring Boot框架:从基础到实践
java·spring·编程语言
晴空月明29 分钟前
JVM 类加载过程与字节码执行深度解析
java
掉鱼的猫1 小时前
Solon AI + MCP实战:5行代码搞定天气查询,LLM从此告别数据孤岛
java·openai·mcp
带刺的坐椅2 小时前
Solon AI + MCP实战:5行代码搞定天气查询,LLM从此告别数据孤岛
java·mcp·solon-ai
androidwork2 小时前
嵌套滚动交互处理总结
android·java·kotlin
草履虫建模2 小时前
Tomcat 和 Spring MVC
java·spring boot·spring·spring cloud·tomcat·mvc·intellij-idea
枣伊吕波3 小时前
第十三节:第七部分:Stream流的中间方法、Stream流的终结方法
java·开发语言
天天摸鱼的java工程师3 小时前
Kafka是如何保证消息队列中的消息不丢失、不重复?
java·后端·kafka