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 减少锁持有时间

  • 优化方向:缩短同步代码块的执行时间,降低锁的竞争概率。
  • 示例
    // 不推荐:锁持有时间过长
    synchronized (lock) {
    // 复杂计算或 IO 操作
    } // 推荐:仅在关键代码块加锁
    int result = doSomeComputation(); // 非同步操作
    synchronized (lock) {
    sharedVariable = result;
    }

5.2 使用分段锁(Segment Locking)

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

5.3 禁用偏向锁

  • 适用场景:频繁切换线程的场景(如高并发服务)。
  • JVM 参数
    -XX:-UseBiasedLocking  # 禁用偏向锁

5.4 调整自旋次数

  • 适用场景:轻量级锁的自旋可能因 CPU 空闲而浪费资源。
  • JVM 参数
    -XX:PreBlockSpin=5  # 设置自旋次数为 5

6. 代码示例

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 的并发性能。


Java 锁升级机制详解的更多相关文章

  1. Java中反射机制详解

    序言 在学习java基础时,由于学的不扎实,讲的实用性不强,就觉得没用,很多重要的知识就那样一笔带过了,像这个马上要讲的反射机制一样,当时学的时候就忽略了,到后来学习的知识中,很多东西动不动就用反射, ...

  2. Java 动态代理机制详解

    在学习Spring的时候,我们知道Spring主要有两大思想,一个是IoC,另一个就是AOP,对于IoC,依赖注入就不用多说了,而对于Spring的核心AOP来说,我们不但要知道怎么通过AOP来满足的 ...

  3. java 锁 Lock接口详解

    一:java.util.concurrent.locks包下常用的类与接口(lock是jdk 1.5后新增的) (1)Lock和ReadWriteLock是两大锁的根接口,Lock代表实现类是Reen ...

  4. Java动态代理机制详解(JDK 和CGLIB,Javassist,ASM)

    class文件简介及加载 Java编译器编译好Java文件之后,产生.class 文件在磁盘中.这种class文件是二进制文件,内容是只有JVM虚拟机能够识别的机器码.JVM虚拟机读取字节码文件,取出 ...

  5. Java 动态代理机制详解(JDK 和CGLIB,Javassist,ASM)

    class文件简介及加载 Java编译器编译好Java文件之后,产生.class 文件在磁盘中.这种class文件是二进制文件,内容是只有JVM虚拟机能够识别的机器码.JVM虚拟机读取字节码文件,取出 ...

  6. Java的内存机制详解

    Java把内存分为两种:一种是栈内存,另一种是堆内存.在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配,当在一段代码块定义一个变量时,Java 就在栈中为这个变量分配内存空间, ...

  7. Java动态代理机制详解(类加载,JDK 和CGLIB,Javassist,ASM)

    class文件简介及加载 Java编译器编译好Java文件之后,产生.class 文件在磁盘中.这种class文件是二进制文件,内容是只有JVM虚拟机能够识别的机器码.JVM虚拟机读取字节码文件,取出 ...

  8. Java 对象序列化机制详解

    对象序列化的目标:将对象保存到磁盘中,或允许在网络中直接传输对象. 对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久的保存在磁盘上,通过网络将这种二进制流传 ...

  9. Java垃圾回收机制详解和调优

    gc即垃圾收集机制是指jvm用于释放那些不再使用的对象所占用的内存.java语言并不要求jvm有gc,也没有规定gc如何工作.不过常用的jvm都有gc,而且大多数gc都使用类似的算法管理内存和执行收集 ...

  10. Java 虚拟机垃圾收集机制详解

    本文摘自深入理解 Java 虚拟机第三版 垃圾收集发生的区域 之前我们介绍过 Java 内存运行时区域的各个部分,其中程序计数器.虚拟机栈.本地方法栈三个区域随线程共存亡.栈中的每一个栈帧分配多少内存 ...

随机推荐

  1. JS处理数据四舍五入(tofixed与round的区别详解)

    1 .tofixed方法 toFixed() 方法可把 Number 四舍五入为指定小数位数的数字.例如将数据Num保留2位小数,则表示为:toFixed(Num):但是其四舍五入的规则与数学中的规则 ...

  2. 队列的实现方式(先进先出 FIFO)--环形队列

    博客地址:https://www.cnblogs.com/zylyehuo/ # -*- coding: utf-8 -*- class Queue: def __init__(self, size= ...

  3. 2D小游戏--猜对应卡牌(unity)

    博客地址:https://www.cnblogs.com/zylyehuo/ 项目名称 guess_card_game 参考源码链接: https://www.manning.com/books/un ...

  4. PVE虚拟机安装详解

    前言 PVE,全称Proxmox Virtual Environment,是基于Debian的Linux系统,虚拟机内核为KVM.硬件兼容性优秀.界面功能不强,很多操作要靠命令行,但扩展能力几乎是无限 ...

  5. delphi 弹出输入框的InputQuery, InputQuery 函数用法

    delphi 弹出输入框的InputQuery, InputQuery 函数用法 procedure TForm1.Button1Click(Sender: TObject); var str: st ...

  6. 方法重写-java se 进阶-day01

    1.方法重写的介绍 当子父类中,某方法存在相同的定义(方法名.参数.返回值)时,子类的方法会将父类的方法进行重写操作(覆盖) 2.方法重写与方法重载的区别 1.方法重载:又称Overload,在同一个 ...

  7. Go 调用系统默认浏览器打开链接

    Go Package 相关包 os/exec 实例 调用Windows系统默认浏览器打开链接 package main import ( "fmt" "os/exec&q ...

  8. fdMemtable FDQuery json之间的一些关系

    1 procedure TForm1.btn1Click(Sender: TObject); 2 var ss,ss1: TStringStream; 3 begin 4 ss := TStringS ...

  9. [设计模式/Java] 设计模式之工厂方法模式【11】

    概述:工厂方法模式 := Factory Method Pattern 工厂模式的3种细分模式: 简单工厂模式 / 工厂方法模式 / 抽象工厂模式 工厂模式(Factory Pattern)是 Jav ...

  10. .net WorkFlow 流程传阅

    WikeFlow官网:www.wikesoft.com WikeFlow学习版演示地址:workflow.wikesoft.com WikeFlow学习版源代码下载:https://gitee.com ...