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 无锁 → 偏向锁
- 触发条件:第一个线程访问同步代码块时。
- 过程:
- JVM 通过 CAS 操作将对象头的
Mark Word标记为偏向锁。 - 记录当前线程 ID 和偏向时间戳。
- 后续同一线程再次访问时,直接通过比对线程 ID 获取锁(无需 CAS 操作)。
- JVM 通过 CAS 操作将对象头的
3.2 偏向锁 → 轻量级锁
- 触发条件:第二个线程尝试获取同一锁(出现竞争)。
- 过程:
- 偏向锁失效,JVM 撤销偏向锁(可能触发 STW,Stop-The-World)。
- 线程通过自旋(Spin)和 CAS 操作尝试获取锁。
- 若自旋成功,则升级为轻量级锁;否则继续自旋或升级为重量级锁。
3.3 轻量级锁 → 重量级锁
- 触发条件:
- 自旋次数超过阈值(默认 10 次,可通过
-XX:PreBlockSpin调整)。 - 多个线程同时竞争锁(如第三个线程加入竞争)。
- 自旋次数超过阈值(默认 10 次,可通过
- 过程:
- JVM 将锁升级为重量级锁,对象头指向监视器(Monitor)。
- 线程进入操作系统内核态的阻塞队列,等待调度器唤醒。
- 未获取锁的线程通过
ObjectMonitor等待唤醒。
4. 锁升级的优缺点
4.1 优点
- 减少无竞争场景的开销:偏向锁和轻量级锁避免了频繁的 CAS 和上下文切换。
- 动态适配竞争强度:在低竞争时保持高性能,在高竞争时保证线程安全。
4.2 缺点
- 偏向锁撤销开销:当其他线程竞争时,撤销偏向锁会导致 STW,影响性能。
- 重量级锁的高开销:在高竞争场景下,频繁的线程阻塞/唤醒会显著降低性能。
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 锁升级机制详解的更多相关文章
- Java中反射机制详解
序言 在学习java基础时,由于学的不扎实,讲的实用性不强,就觉得没用,很多重要的知识就那样一笔带过了,像这个马上要讲的反射机制一样,当时学的时候就忽略了,到后来学习的知识中,很多东西动不动就用反射, ...
- Java 动态代理机制详解
在学习Spring的时候,我们知道Spring主要有两大思想,一个是IoC,另一个就是AOP,对于IoC,依赖注入就不用多说了,而对于Spring的核心AOP来说,我们不但要知道怎么通过AOP来满足的 ...
- java 锁 Lock接口详解
一:java.util.concurrent.locks包下常用的类与接口(lock是jdk 1.5后新增的) (1)Lock和ReadWriteLock是两大锁的根接口,Lock代表实现类是Reen ...
- Java动态代理机制详解(JDK 和CGLIB,Javassist,ASM)
class文件简介及加载 Java编译器编译好Java文件之后,产生.class 文件在磁盘中.这种class文件是二进制文件,内容是只有JVM虚拟机能够识别的机器码.JVM虚拟机读取字节码文件,取出 ...
- Java 动态代理机制详解(JDK 和CGLIB,Javassist,ASM)
class文件简介及加载 Java编译器编译好Java文件之后,产生.class 文件在磁盘中.这种class文件是二进制文件,内容是只有JVM虚拟机能够识别的机器码.JVM虚拟机读取字节码文件,取出 ...
- Java的内存机制详解
Java把内存分为两种:一种是栈内存,另一种是堆内存.在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配,当在一段代码块定义一个变量时,Java 就在栈中为这个变量分配内存空间, ...
- Java动态代理机制详解(类加载,JDK 和CGLIB,Javassist,ASM)
class文件简介及加载 Java编译器编译好Java文件之后,产生.class 文件在磁盘中.这种class文件是二进制文件,内容是只有JVM虚拟机能够识别的机器码.JVM虚拟机读取字节码文件,取出 ...
- Java 对象序列化机制详解
对象序列化的目标:将对象保存到磁盘中,或允许在网络中直接传输对象. 对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久的保存在磁盘上,通过网络将这种二进制流传 ...
- Java垃圾回收机制详解和调优
gc即垃圾收集机制是指jvm用于释放那些不再使用的对象所占用的内存.java语言并不要求jvm有gc,也没有规定gc如何工作.不过常用的jvm都有gc,而且大多数gc都使用类似的算法管理内存和执行收集 ...
- Java 虚拟机垃圾收集机制详解
本文摘自深入理解 Java 虚拟机第三版 垃圾收集发生的区域 之前我们介绍过 Java 内存运行时区域的各个部分,其中程序计数器.虚拟机栈.本地方法栈三个区域随线程共存亡.栈中的每一个栈帧分配多少内存 ...
随机推荐
- GBJ 97-1987 水泥混凝土路面施工及验收规范(电子版)PDF 版本 下载
本规范适用于新建和改建的公路 城市道路 厂矿道路和民航机场道面等就地浇筑的水泥混凝土路面的施工及验收 链接:https://pan.baidu.com/s/17t88jnEU6IrptmEWsyuN3 ...
- Git--命令常用
GITLab 命令 git remote add origin https://gitee.com/gtnotgod/Data-Quality-Management.git #增加了远程仓库 git ...
- CentOS 版本选择:DVD、Everything、LiveCD、Minimal、NetInstall
CentOS 7.X,主要是下载的时候有很多版本供选择,如何选择? DVD版:这个是常用版本,就是普通安装版了,推荐大家安装.里面包含大量的常用软件,大部分情况下安装时无需再在线下载,体积为4G.Ev ...
- Delphi 判断操作系统是32位或是64位
function IsWin64: Boolean; var Kernel32Handle: THandle; IsWow64Process: function(Handle: Windows.THa ...
- pandas数据统一绘图风格配置
在使用pandas的时候,经常会用到Dataframe或者Series的plot方法,该方法底层实际上调的还是matplotlib.pyplot的plot方法.因此,通过对pyplot模块的绘图全局参 ...
- 【Git】基本操作
一.Git 基础 1.Git 介绍 Git 是目前世界上最先进的分布式版本控制系统. 版本控制系统: 设计师在设计的时候做了很多版本 经过了数天去问设计师每个版本都改了些啥,设计师此时可能就说不上来了 ...
- 《机器人SLAM导航核心技术与实战》先导课:课程大纲
<机器人SLAM导航核心技术与实战>先导课:课程大纲 视频讲解 [先导课]1.课程大纲-视频讲解 [先导课]1.1.课程大纲-学习思维导图(上)-视频讲解 [先导课]1.2.课程大纲-学习 ...
- 跨网段和局域网的SQL SERVER发布订阅配置图解和常见问题
非常详细,傻瓜式依葫芦画瓢即可. 特别提示:订阅机器上的防火墙以及发布机器远程登录订阅机的问题 通过非命令行方式配置同步订阅 (1)实验环境说明 (2)实验前准备 (3)订阅设置 (4)测试同步订阅 ...
- xe10.3+paserver在Ubuntu下运行错误
xe.3的paserver在Ubuntu下执行呈现乱七八糟的错误提示. 原因:Ubuntu的版本和paserver编译的环境不一致. 注意:使用ARM64的版本.如ubuntu-18.04.2-des ...
- docker发布简单python服务
进入机器创建一个目录mkdir dockerbuild1.编写简单flask代码vi flaskapp.pyfrom flask import Flaskimport os app = Flask(_ ...