synchronized 优化手段之锁膨胀机制!
synchronized 在 JDK 1.5 之前性能是比较低的,在那时我们通常会选择使用 Lock 来替代 synchronized。然而这个情况在 JDK 1.6 时就发生了改变,JDK 1.6 中对 synchronized 进行了各种优化,性能也得到了大幅的提升,这也是目前版本中还能经常见到 synchronized 身影的重要原因之一。当然除了性能之外,synchronized 的使用也非常便利,这也是它流行的重要原因。
在众多优化方案中,锁膨胀机制是提升 synchronized 性能最有利的手段之一(其他优化方案我们后面再讲),本文我们重点来看什么是锁膨胀?以及锁膨胀的各种细节。
正文
在 JDK 1.5 时,synchronized 需要调用监视器锁(Monitor)来实现,监视器锁本质上又是依赖于底层的操作系统的 Mutex Lock(互斥锁)实现的,互斥锁在进行释放和获取的时候,需要从用户态转换到内核态,这样就造成了很高的成本,也需要较长的执行时间,这种依赖于操作系统 Mutex Lock 实现的锁我们称之为“重量级锁”。
什么是用户态和内核态?
用户态(User Mode):当进程在执行用户自己的代码时,则称其处于用户运行态。
内核态(Kernel Mode):当一个任务(进程)执行系统调用而陷入内核代码中执行时,我们就称进程处于内核运行态,此时处理器处于特权级最高的内核代码中执行。
为什么分内核态和用户态?
假设没有内核态和用户态之分,程序就可以随意读写硬件资源了,比如随意读写和分配内存,这样如果程序员一不小心将不适当的内容写到了不该写的地方,很可能就会导致系统崩溃。
而有了用户态和内核态的区分之后,程序在执行某个操作时会进行一系列的验证和检验之后,确认没问题之后才可以正常的操作资源,这样就不会担心一不小心就把系统搞坏的情况了,也就是有了内核态和用户态的区分之后可以让程序更加安全的运行,但同时两种形态的切换会导致一定的性能开销。
锁膨胀
在 JDK 1.6 时,为了解决获取锁和释放锁带来的性能消耗,引入了“偏向锁”和“轻量级锁”的状态,此时 synchronized 的状态总共有以下 4 种:
- 无锁
- 偏向锁
- 轻量级锁
- 重量级锁
锁的级别按照上述先后顺序依次升级,我们把这个升级的过程称之为“锁膨胀”。
PS:到现在为止,锁的升级是单向的,也就是说只能从低到高升级(无锁 -> 偏向锁 -> 轻量锁锁 -> 重量级锁),不会出现锁降级的情况。
锁膨胀为什么能优化 synchronized 的性能?当我们了解了这些锁状态之后自然就会有答案,下面我们一起来看。
1.偏向锁
HotSpot 作者经过研究实践发现,在大多数情况下,锁不存在多线程竞争,总是由同一线程多次获得的,为了让线程获得锁的代价更低,于是就引进了偏向锁。
偏向锁(Biased Locking)指的是,它会偏向于第一个访问锁的线程,如果在运行过程中,同步锁只有一个线程访问,不存在多线程争用的情况,则线程是不需要触发同步的,这种情况下会给线程加一个偏向锁。
偏向锁执行流程
当一个线程访问同步代码块并获取锁时,会在对象头的 Mark Word 里存储锁偏向的线程 ID,在线程进入和退出同步块时不再通过 CAS 操作来加锁和解锁,而是检测 Mark Word 里是否存储着指向当前线程的偏向锁,如果 Mark Word 中的线程 ID 和访问的线程 ID 一致,则可以直接进入同步块进行代码执行,如果线程 ID 不同,则使用 CAS 尝试获取锁,如果获取成功则进入同步块执行代码,否则会将锁的状态升级为轻量级锁。
偏向锁的优点
偏向锁是为了在无多线程竞争的情况下,尽量减少不必要的锁切换而设计的,因为锁的获取及释放要依赖多次 CAS 原子指令,而偏向锁只需要在置换线程 ID 的时候执行一次 CAS 原子指令即可。
Mark Word 扩展知识:内存布局
在 HotSpot 虚拟机中,对象在内存中存储的布局可以分为以下 3 个区域:
- 对象头(Header)
- 实例数据(Instance Data)
- 对齐填充(Padding)
对象头中又包含了:
- Mark Word(标记字段):我们的偏向锁信息就是存储在此区域的。
- Klass Pointer(Class 对象指针)
对象在内存中的布局如下:
在 JDK 1.6 中默认是开启偏向锁的,可以通过“-XX:-UseBiasedLocking=false”命令来禁用偏向锁。
2.轻量级锁
引入轻量级锁的目的是在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统 Mutex Lock(互斥锁)产生的性能消耗。如果使用 Mutex Lock 每次获取锁和释放锁的操作都会带来用户态和内核态的切换,这样系统的性能开销是很大的。
当关闭偏向锁或者多个线程竞争偏向锁时就会导致偏向锁升级为轻量级锁,轻量级锁的获取和释放都通过 CAS 完成的,其中锁获取可能会通过一定次数的自旋来完成。
注意事项
需要强调一点:轻量级锁并不是用来代替重量级锁的,它的本意是在没有多线程竞争的前提下,减少传统的重量级锁使用产生的性能消耗。轻量级锁所适应的场景是线程交替执行同步块的情况,如果同一时间多个线程同时访问时,就会导致轻量级锁膨胀为重量级锁。
3.重量级锁
synchronized 是依赖监视器 Monitor 实现方法同步或代码块同步的,代码块同步使用的是 monitorenter 和 monitorexit 指令来实现的,monitorenter 指令是在编译后插入到同步代码块的开始位置,而 monitorexit 是插入到方法结束处和异常处的,任何对象都有一个 Monitor 与之关联,当且一个 Monitor 被持有后,它将处于锁定状态。
如以下加锁代码:
public class SynchronizedToMonitorExample {
public static void main(String[] args) {
int count = 0;
synchronized (SynchronizedToMonitorExample.class) {
for (int i = 0; i < 10; i++) {
count++;
}
}
System.out.println(count);
}
}
当我们将上述代码编译成字节码之后,它的内容是这样的:
从上述结果可以看出,在 main 方法的执行中多个 monitorenter 和 monitorexit 的指令,由此可知 synchronized 是依赖 Monitor 监视器锁实现的,而监视器锁又是依赖操作系统的互斥锁(Mutex Lock),互斥锁在每次获取和释放锁时,都会带来用户态和内核态的切换,这样就增加了系统的性能开销。
总结
synchronized 在 JDK 1.6 时优化了其性能,在一系列优化的手段中,锁膨胀是提升 synchronized 执行效率的关键手段之一,锁膨胀指的是 synchronized 会从无锁状态、到偏向锁、到轻量级锁,最后到重量级锁的过程。重量级之前的所有状态在绝大数情况下可以大幅的提升 synchronized 的性能。
本系列推荐文章
- 并发第一课:Thread 详解
- Java中用户线程和守护线程区别这么大?
- 深入理解线程池 ThreadPool
- 线程池的7种创建方式,强烈推荐你用它...
- 池化技术到达有多牛?看了线程和线程池的对比吓我一跳!
- 并发中的线程同步与锁
- synchronized 加锁 this 和 class 的区别!
- volatile 和 synchronized 的区别
- 轻量级锁一定比重量级锁快吗?
- 这样终止线程,竟然会导致服务宕机?
- SimpleDateFormat线程不安全的5种解决方案!
- ThreadLocal不好用?那是你没用对!
- ThreadLocal内存溢出代码演示和原因分析!
- Semaphore自白:限流器用我就对了!
- CountDownLatch:别浪,等人齐再团!
- CyclicBarrier:人齐了,司机就可以发车了!
关注公号「Java中文社群」查看更多有意思、涨知识的 Java 并发文章。
synchronized 优化手段之锁膨胀机制!的更多相关文章
- synchronized优化手段:锁膨胀、锁消除、锁粗化和自适应自旋锁...
synchronized 在 JDK 1.5 时性能是比较低的,然而在后续的版本中经过各种优化迭代,它的性能也得到了前所未有的提升,上一篇中我们谈到了锁膨胀对 synchronized 性能的提升,然 ...
- synchronized的实现原理——锁膨胀过程
@ 目录 前言 正文 偏向锁 轻量锁 批量重偏向 批量撤销 重量锁 总结 前言 上一篇分析了优化后的synchronized在不同场景下对象头中的表现形式,还记得那个结论吗?当一个线程第一次获取锁后再 ...
- Java基础之(一)——从synchronized优化看Java锁概念
一.悲观锁和乐观锁概念 悲观锁和乐观锁是一种广义的锁概念,Java中没有哪个Lock实现类就叫PessimisticLock或OptimisticLock,而是在数据并发情况下的两种不同处理策略. 针 ...
- synchronized(三) 锁的膨胀过程(锁的升级过程)深入剖析
警告⚠️:本文耗时很长,先做好心理准备................哈哈哈 本篇我们讲通过大量实例代码及hotspot源码分析偏向锁(批量重偏向.批量撤销).轻量级锁.重量级锁及锁的膨胀过程(也就是 ...
- synchronized实现原理及其优化-(自旋锁,偏向锁,轻量锁,重量锁)
1.synchronized概述: synchronized修饰的方法或代码块相当于并发中的临界区,即在同一时刻jvm只允许一个线程进入执行.synchronized是通过锁机制实现同一时刻只允许一个 ...
- synchronized的锁升级/锁膨胀
偏向锁 偏向第一个拿到锁的线程. 即第一个拿到锁的线程,锁会在对象头 Mark Word 中通过 CAS 记录该线程 ID,该线程以后每次拿锁时都不需要进行 CAS(指轻量级锁). 如果该线程正在执行 ...
- JVM中锁优化,偏向锁、自旋锁、锁消除、锁膨胀
详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt364 本文将简单介绍HotSpot虚拟机中用到的锁优化技术. 自旋锁 互斥同 ...
- Java并发编程:Synchronized底层优化(偏向锁、轻量级锁)
Java并发编程系列: Java 并发编程:核心理论 Java并发编程:Synchronized及其实现原理 Java并发编程:Synchronized底层优化(轻量级锁.偏向锁) Java 并发编程 ...
- 并发-Synchronized底层优化(偏向锁、轻量级锁)
Synchronized底层优化(偏向锁.轻量级锁) 参考: http://www.cnblogs.com/paddix/p/5405678.html 一.重量级锁 上篇文章中向大家介绍了Synchr ...
随机推荐
- Python进程和线程实例详解
前言 进程是什么? 进程就是一个程序在一个数据集上的一次动态执行过程.进程一般由程序.数据集.进程控制块三部分组成.我们编写的程序用来描述进程要完成哪些功能以及如何完成:数据集则是程序在执行过程中所需 ...
- element的日期选择使用value-format之后表单验证报错
在表单验证的时候报错 添加一个日期控件,但是发现在表单验证中遇到了冲突如下: Error in event handler for "el.form.change": " ...
- sql循环说明
while循环:主要是判断,不能使用表中的ID,临时表是ID自增的,通过自增ID可以查出表ID(语法简单,需要配合其他代码操作表ID)游标循环:可以使用表中的ID ,进行修改等操作(语法难一点,核心代 ...
- 37.qt quick- 高仿微信实现局域网聊天V3版本(添加登录界面、UDP校验登录、皮肤更换、3D旋转)
1.版本介绍(已上传至群里) 版本说明: 添加登录界面. UDP校验登录. 皮肤更换. 3D旋转(主界面和登录界面之间切换) . 效果图如下所示: 如果效果图加载失败,可以去哔哩哔哩 https:// ...
- 确保 PHP 应用程序的安全 -- 不能违反的四条安全规则(转)
规则 1:绝不要信任外部数据或输入 关于 Web 应用程序安全性,必须认识到的第一件事是不应该信任外部数据.外部数据(outside data) 包括不是由程序员在 PHP 代码中直接输入的任何数 ...
- Lua表达式
目录 算术运算符 关系运算符 逻辑运算符 字符串连接(more) string.format转义符的使用 优先级 算术运算符 Lua 的算术运算符如下表所示: 示例代码:$expr1.lua prin ...
- 洛谷P5463 小鱼比可爱(加强版) 题解
写博客不易,来玩会? 这道题我和dalao们的做法略有不同,我用的是归并排序做法qwq 归并排序求逆序对大家应该很清楚了,我这里就来讲讲如何用归并排序求出这道题的答案 让我们先观察一下规律 举个栗子, ...
- 题解 guP4552 IncDec Sequence
这道题是一道差分的题目 差分数组p即p[i]=a[i]-a[i-1] 如果我们把一个区间[l,r]里的数+1,那么我们不难发现p[l]'=a[l]+1-a[l-1]=p[l]+1,p[r+1]'=a[ ...
- adb bat 改进
@REM 生成随机数@echo off@REM 设置延迟变量setlocal enabledelayedexpansionset min=9set max=17set /a mod=!max!-!mi ...
- python3安装pp过程
并行计算的目的是将所有的核心都运行起来以提高代码的执行速度,在python中由于存在全局解释器锁(GIL)如果使用默认的python多线程进行并行计算可能会发现代码的执行速度并不会加快,甚至会比使用但 ...