synchronized优化手段:锁膨胀、锁消除、锁粗化和自适应自旋锁...
synchronized 在 JDK 1.5 时性能是比较低的,然而在后续的版本中经过各种优化迭代,它的性能也得到了前所未有的提升,上一篇中我们谈到了锁膨胀对 synchronized 性能的提升,然而它也只是“众多” synchronized 性能优化方案中的一种,那么我们本文就来盘点一下 synchronized 的核心优化方案。
synchronized 核心优化方案主要包含以下 4 个:
- 锁膨胀
- 锁消除
- 锁粗化
- 自适应自旋锁
1.锁膨胀
我们先来回顾一下锁膨胀对 synchronized 性能的影响,所谓的锁膨胀是指 synchronized 从无锁升级到偏向锁,再到轻量级锁,最后到重量级锁的过程,它叫做锁膨胀也叫做锁升级。
JDK 1.6 之前,synchronized 是重量级锁,也就是说 synchronized 在释放和获取锁时都会从用户态转换成内核态,而转换的效率是比较低的。但有了锁膨胀机制之后,synchronized 的状态就多了无锁、偏向锁以及轻量级锁了,这时候在进行并发操作时,大部分的场景都不需要用户态到内核态的转换了,这样就大幅的提升了 synchronized 的性能。
PS:至于为什么不需要用户态到内核态的转换?请移步到锁膨胀的那篇文章:《synchronized 优化手段之锁膨胀机制》。
2.锁消除
很多人都了解 synchronized 中锁膨胀的机制,但对接下来的 3 项优化却知之甚少,这样会在面试中错失良机,那么我们本文就把这 3 项优化单独拎出来讲一下吧。
锁消除指的是在某些情况下,JVM 虚拟机如果检测不到某段代码被共享和竞争的可能性,就会将这段代码所属的同步锁消除掉,从而到底提高程序性能的目的。
锁消除的依据是逃逸分析的数据支持,如 StringBuffer 的 append() 方法,或 Vector 的 add() 方法,在很多情况下是可以进行锁消除的,比如以下这段代码:
public String method() {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < 10; i++) {
sb.append("i:" + i);
}
return sb.toString();
}
以上代码经过编译之后的字节码如下:
从上述结果可以看出,之前我们写的线程安全的加锁的 StringBuffer 对象,在生成字节码之后就被替换成了不加锁不安全的 StringBuilder 对象了,原因是 StringBuffer 的变量属于一个局部变量,并且不会从该方法中逃逸出去,所以此时我们就可以使用锁消除(不加锁)来加速程序的运行。
3.锁粗化
锁粗化是指,将多个连续的加锁、解锁操作连接在一起,扩展成一个范围更大的锁。
我只听说锁“细化”可以提高程序的执行效率,也就是将锁的范围尽可能缩小,这样在锁竞争时,等待获取锁的线程才能更早的获取锁,从而提高程序的运行效率,但锁粗化是如何提高性能的呢?
没错,锁细化的观点在大多数情况下都是成立了,但是一系列连续加锁和解锁的操作,也会导致不必要的性能开销,从而影响程序的执行效率,比如这段代码:
public String method() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10; i++) {
// 伪代码:加锁操作
sb.append("i:" + i);
// 伪代码:解锁操作
}
return sb.toString();
}
这里我们不考虑编译器优化的情况,如果在 for 循环中定义锁,那么锁的范围很小,但每次 for 循环都需要进行加锁和释放锁的操作,性能是很低的;但如果我们直接在 for 循环的外层加一把锁,那么对于同一个对象操作这段代码的性能就会提高很多,如下伪代码所示:
public String method() {
StringBuilder sb = new StringBuilder();
// 伪代码:加锁操作
for (int i = 0; i < 10; i++) {
sb.append("i:" + i);
}
// 伪代码:解锁操作
return sb.toString();
}
锁粗化的作用:如果检测到同一个对象执行了连续的加锁和解锁的操作,则会将这一系列操作合并成一个更大的锁,从而提升程序的执行效率。
4.自适应自旋锁
自旋锁是指通过自身循环,尝试获取锁的一种方式,伪代码实现如下:
// 尝试获取锁
while(!isLock()){
}
自旋锁优点在于它避免一些线程的挂起和恢复操作,因为挂起线程和恢复线程都需要从用户态转入内核态,这个过程是比较慢的,所以通过自旋的方式可以一定程度上避免线程挂起和恢复所造成的性能开销。
但是,如果长时间自旋还获取不到锁,那么也会造成一定的资源浪费,所以我们通常会给自旋设置一个固定的值来避免一直自旋的性能开销。然而对于 synchronized 关键字来说,它的自旋锁更加的“智能”,synchronized 中的自旋锁是自适应自旋锁,这就好比之前一直开的手动挡的三轮车,而经过了 JDK 1.6 的优化之后,我们的这部“车”,一下子变成自动挡的兰博基尼了。
自适应自旋锁是指,线程自旋的次数不再是固定的值,而是一个动态改变的值,这个值会根据前一次自旋获取锁的状态来决定此次自旋的次数。比如上一次通过自旋成功获取到了锁,那么这次通过自旋也有可能会获取到锁,所以这次自旋的次数就会增多一些,而如果上一次通过自旋没有成功获取到锁,那么这次自旋可能也获取不到锁,所以为了避免资源的浪费,就会少循环或者不循环,以提高程序的执行效率。简单来说,如果线程自旋成功了,则下次自旋的次数会增多,如果失败,下次自旋的次数会减少。
总结
本文我们介绍了 4 种优化 synchronized 的方案,其中锁膨胀和自适应自旋锁是 synchronized 关键字自身的优化实现,而锁消除和锁粗化是 JVM 虚拟机对 synchronized 提供的优化方案,这些优化方案最终使得 synchronized 的性能得到了大幅的提升,也让它在并发编程中占据了一席之地。
参考 & 鸣谢
www.cnblogs.com/aspirant/p/11470858.html
zhuanlan.zhihu.com/p/29866981
tech.meituan.com/2018/11/15/java-lock.html
本系列推荐文章
- 并发第一课:Thread 详解
- Java中用户线程和守护线程区别这么大?
- 深入理解线程池 ThreadPool
- 线程池的7种创建方式,强烈推荐你用它...
- 池化技术到达有多牛?看了线程和线程池的对比吓我一跳!
- 并发中的线程同步与锁
- synchronized 加锁 this 和 class 的区别!
- volatile 和 synchronized 的区别
- 轻量级锁一定比重量级锁快吗?
- 这样终止线程,竟然会导致服务宕机?
- SimpleDateFormat线程不安全的5种解决方案!
- ThreadLocal不好用?那是你没用对!
- ThreadLocal内存溢出代码演示和原因分析!
- Semaphore自白:限流器用我就对了!
- CountDownLatch:别浪,等人齐再团!
- CyclicBarrier:人齐了,司机就可以发车了!
- synchronized 优化手段之锁膨胀机制!
synchronized优化手段:锁膨胀、锁消除、锁粗化和自适应自旋锁...的更多相关文章
- synchronized 优化手段之锁膨胀机制!
synchronized 在 JDK 1.5 之前性能是比较低的,在那时我们通常会选择使用 Lock 来替代 synchronized.然而这个情况在 JDK 1.6 时就发生了改变,JDK 1.6 ...
- 重量级锁synchronized的优化----自旋锁、自适应自旋锁、锁消除、锁粗化
synchronized是重量级锁,效率不高.但在jdk 1.6中对synchronize的实现进行了各种优化,使得它显得不是那么重了.jdk1.6对锁的实现引入了大量的优化,如自旋锁.自适应自旋锁. ...
- synchronized底层实现原理&CAS操作&偏向锁、轻量级锁,重量级锁、自旋锁、自适应自旋锁、锁消除、锁粗化
进入时:monitorenter 每个对象有一个监视器锁(monitor).当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:1 ...
- synchronized的锁升级/锁膨胀
偏向锁 偏向第一个拿到锁的线程. 即第一个拿到锁的线程,锁会在对象头 Mark Word 中通过 CAS 记录该线程 ID,该线程以后每次拿锁时都不需要进行 CAS(指轻量级锁). 如果该线程正在执行 ...
- Java基础之(一)——从synchronized优化看Java锁概念
一.悲观锁和乐观锁概念 悲观锁和乐观锁是一种广义的锁概念,Java中没有哪个Lock实现类就叫PessimisticLock或OptimisticLock,而是在数据并发情况下的两种不同处理策略. 针 ...
- Java 中15种锁的介绍:公平锁,可重入锁,独享锁,互斥锁,乐观锁,分段锁,自旋锁等等
Java 中15种锁的介绍 Java 中15种锁的介绍:公平锁,可重入锁,独享锁,互斥锁,乐观锁,分段锁,自旋锁等等,在读很多并发文章中,会提及各种各样锁如公平锁,乐观锁等等,这篇文章介绍各种锁的分类 ...
- 偏向锁,偏向线程id ,自旋锁
理解锁的基础知识 如果想要透彻的理解Java锁的来龙去脉,需要先了解以下基础知识. 基础知识之一:锁的类型 锁从宏观上分类,分为悲观锁与乐观锁. 乐观锁 乐观锁是一种乐观思想,即认为读多写少,遇到并发 ...
- Java线程并发中常见的锁--自旋锁 偏向锁
随着互联网的蓬勃发展,越来越多的互联网企业面临着用户量膨胀而带来的并发安全问题.本文着重介绍了在java并发中常见的几种锁机制. 1.偏向锁 偏向锁是JDK1.6提出来的一种锁优化的机制.其核心的思想 ...
- 漫画|Linux 并发、竞态、互斥锁、自旋锁、信号量都是什么鬼?(转)
知乎链接:https://zhuanlan.zhihu.com/p/57354304 1. 锁的由来? 学习linux的时候,肯定会遇到各种和锁相关的知识,有时候自己学好了一点,感觉半桶水的自己已经可 ...
随机推荐
- linux 查看目录大小
查看当前目录下各个目录大小容量 du -sh * du -sh /app/* du -h --max-depth=1 .
- 【重学Java】Set集合
Set集合 Set集合概述和特点[应用] 无序不可重复 没有索引,不能使用普通for循环遍历.可以使用迭代器或者增强foreach语句遍历 TreeSet集合 TreeSet集合概述和特点[应用] 无 ...
- JVM,我就不信学不会你了
JVM 对 Java 有多重要,对程序员面试有多重要,这些不用多说. 如果你还没意识到学 JVM 的必要性,或者不知道怎么学 JVM,那么看完这篇文章,你就能知道答案了. 曾经的我很不屑于学 JVM, ...
- SpringBoot | 1.3 约定编程Spring AOP
前言 前面聊过Spring的一个很重要的概念,IoC控制反转,接下来就是AOP了: 1. AOP切面编程 面向切面编程,是利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度 ...
- Docker:DockerFile详解与实例
基本结构 Dockerfile 由一行行命令语句组成,并且支持已 # 开头的注释行. 一般而言,Dockerfile 的内容分为四个部分: 基础镜像信息. 维护者信息. 镜像操作指令. 容器启动时执行 ...
- 1.3.8、通过RemoteAddr匹配
server: port: 8080 spring: application: name: gateway cloud: gateway: routes: - id: guo-system4 uri: ...
- 合并N个长度为M的有序数组为一个N*M的有序数组
题目:合并N个有序数组,每个数组的长度为M,合并为N*M的有序数组.时间复杂度要求最低 解法:N个数组进行两两合并,合并后的数组再继续执行合并过程,最后合成N*M的有序数组.可以认为合并这个递归过程发 ...
- Ubuntu创建图标
起因 安装一些软件时,总是没有图标,导致无法固定到docky栏,所以极为不方便,所以需要自己创建图标. 操作 以创建微信图标为例 [Desktop Entry] Name=Wecaht #名字 Com ...
- 去抖动 debounce
最近才发现 debounce 在 减少DOM操作及资源加载方面得重要性,遂写个博客记录一下 /** * @param {Function} func 要执行的函数 * @param {number} ...
- 「AGC025D」 Choosing Points
「AGC025D」 Choosing Points 神仙构造题. 首先你会尝试暴力做,先随便选一个点,然后把当前能选得全选上,然后你发现这样样例都过不了. 然后我们可以这样考虑:你把距离为 \(\sq ...