synchronized 优化手段之锁膨胀机制!

作者 | 王磊

来源 | Java中文社群(ID:javacn666)

转载请联系授权(微信ID:GG_Stone)

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 种:

  1. 无锁

  2. 偏向锁

  3. 轻量级锁

  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 个区域:

  1. 对象头(Header)

  2. 实例数据(Instance Data)

  3. 对齐填充(Padding)

对象头中又包含了:

  1. Mark Word(标记字段):我们的偏向锁信息就是存储在此区域的

  2. 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 的性能。

本系列推荐文章

1.线程的故事:我的3位母亲成就了优秀的我!

2.线程池的7种创建方式,强烈推荐你用它...

3.轻量级锁一定比重量级锁快吗?

4.这样终止线程,竟然会导致服务宕机?

5.漫画:如何证明sleep不释放锁,而wait释放锁?

6.池化技术到达有多牛?看了线程和线程池的对比吓我一跳!

7.求求你,别再用wait和notify了!

8.Semaphore自白:限流器用我就对了!

9.CountDownLatch:别浪,等人齐再团!

10.CyclicBarrier:人齐了,老司机就发车了!

11.Java中用户线程和守护线程区别这么大?

12.ThreadLocal不好用?那是你没用对!

13.ThreadLocal内存溢出代码演示和原因分析!

14.SimpleDateFormat线程不安全的5种解决方案!

15.synchronized 加锁 this 和 class 的区别!


往期推荐

synchronized 加锁 this 和 class 的区别!


使用了synchronized,竟然还有线程安全问题!


图解Java中的18 把锁!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/544652.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

NTFS USN的Create和工具代码汇总

1、 因为之前把相关代码放在了GitHub上&#xff0c;后来突然有人帮忙改了些个BUG&#xff0c;非常感谢 760193107&#xff0c;所以就写了个完整点的例子&#xff0c;希望对别人有所帮助。 GitHub项目地址 2、错误码&#xff1a;ERROR_JOURNAL_NOT_ACTIVE 在测试时&#xff…

在Java中,负数的绝对值不一定是正数!

作者 l Hollis来源 l Hollis&#xff08;ID&#xff1a;hollischuang&#xff09;绝对值是指一个数在数轴上所对应点到原点的距离&#xff0c;所以&#xff0c;在数学领域&#xff0c;正数的绝对值是这个数本身&#xff0c;负数的绝对值应该是他的相反数。这几乎是每个人都知道…

自己写着玩(二)

转载于:https://www.cnblogs.com/wangmengmeng/p/4572611.html

实战:隐藏SpringBoot中的私密数据!

这几天公司在排查内部数据账号泄漏&#xff0c;原因是发现某些实习生小可爱居然连带着账号、密码将源码私传到GitHub上&#xff0c;导致核心数据外漏&#xff0c;孩子还是没挨过社会毒打&#xff0c;这种事的后果可大可小。说起这个我是比较有感触的&#xff0c;之前我TM被删库…

JS的条形码和二维码生成

一、前言 最近做项目用到了JS生成条形码和二维码&#xff0c;内容不多&#xff0c;整理一下方便使用。 2018年7月5日更新&#xff1a; 二维码生成时&#xff0c;如果长度太长会有异常&#xff1a; Uncaught Error: code length overflow. (1604>1056) 创建的时候&#…

synchronized 中的 4 个优化,你知道几个?

作者 | 王磊来源 | Java中文社群&#xff08;ID&#xff1a;javacn666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone&#xff09;synchronized 在 JDK 1.5 时性能是比较低的&#xff0c;然而在后续的版本中经过各种优化迭代&#xff0c;它的性能也得到了前…

31Exchange Server 2010跨站点部署-搬迁Exchange服务器到分支机构

16.4 将EX07和EX08搬迁到上海分支机构首先我在上海分支机机构站点下创建一个CAS阵列&#xff0c;命令如下&#xff1a;下面获取下当前域中的CAS阵列信息16.4.1搬迁CAS,HT服务器1、从广州总部NLB群集删除EX07主机2、修改EX07的IP地址为分支机构IP地址 192.168.20.27(上海分支机构…

@Autowired的这些骚操作,你都知道吗?

前言最近review别人代码的时候&#xff0c;看到了一些Autowired不一样的用法&#xff0c;觉得有些意思&#xff0c;特定花时间研究了一下&#xff0c;收获了不少东西&#xff0c;现在分享给大家。也许Autowired比你想象中更强大。1. Autowired的默认装配我们都知道在spring中Au…

C# Winform 使用二维码

关于C# Winform 程序中使用二维码的使用记录&#xff1a; 1、使用 Nuget 安装 ZXing.Net 程序包&#xff1b; 2、调用代码&#xff1a; private void button1_Click(object sender, EventArgs e) {BarcodeWriter writer new BarcodeWriter();writer.Format BarcodeFormat…

[Swust OJ 85]--单向公路(BFS)

题目链接:http://acm.swust.edu.cn/problem/0085/ Time limit(ms): 5000      Memory limit(kb): 65535Description某个地区有许多城镇&#xff0c;但并不是每个城镇都跟其他城镇有公路连接&#xff0c;且有公路的并不都能双向行驶。现在我们把这些城镇间的公路分布及允许…

7 种分布式全局 ID 生成策略,你更爱哪种?

上了微服务之后&#xff0c;很多原本很简单的问题现在都变复杂了&#xff0c;例如全局 ID 这事&#xff01;最近工作中刚好用到这块内容&#xff0c;于是调研了市面上几种常见的全局 ID 生成策略&#xff0c;稍微做了一下对比&#xff0c;供小伙伴们参考。当数据库分库分表之后…

C# 读取照片的EXIF信息

一、使用 MetadataExtractor 读取 EXIF 信息 1、NuGet 中安装 在 NuGet 中搜索并安装 MetadataExtractor&#xff1b; 2、包信息 我安装后会有两个包&#xff1a;MetadataExtractor 2.0.0 和 XmpCore 5.1.3 3、代码实现 我是创建的 WPF 项目&#xff1a; private void B…

ReentrantLock 中的 4 个坑!

作者 | 王磊来源 | Java中文社群&#xff08;ID&#xff1a;javacn666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone&#xff09;JDK 1.5 之前 synchronized 的性能是比较低的&#xff0c;但在 JDK 1.5 中&#xff0c;官方推出一个重量级功能 Lock&#x…

SpringBoot 中 4 大核心组件,你了解多少?

Spring Boot 中的 4 大组件分别是&#xff1a;Spring Boot Starter、Spring Boot Autoconfigure、Spring Boot CLI 以及 Spring Boot actuator&#xff0c;接下来&#xff0c;我们分别来看他们的使用和作用。1.Spring Boot Starter1.1 Starter的应用示例<dependency><…

双重检查锁,原来是这样演变来的,你了解吗

最近在看Nacos的源代码时&#xff0c;发现多处都使用了“双重检查锁”的机制&#xff0c;算是非常好的实践案例。这篇文章就着案例来分析一下双重检查锁的使用以及优势所在&#xff0c;目的就是让你的代码格调更加高一个层次。同时&#xff0c;基于单例模式&#xff0c;讲解一下…

WakaTime 记录你的时间(Moana 自动同步信息客户端)

X、写在前面 代码界有一神器&#xff0c;可以记录敲代码的时间&#xff0c;项目名称&#xff0c;编译器等信息&#xff0c;可以极大的满足程序员的虚荣心&#xff0c;它就是 WakaTime 网站链接 WakaTime 可以记录敲代码时间&#xff0c;和具体编辑的文件等信息&#xff0c;并…

图解:为什么非公平锁的性能更高?

作者 | 王磊来源 | Java中文社群&#xff08;ID&#xff1a;javacn666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone&#xff09;在 Java 中 synchronized 和 ReentrantLock 默认使用的都是非公平锁&#xff0c;而它们采用非公平锁的原因都是一致的&#…

死锁的 4 种排查工具 !

作者 | 王磊来源 | Java中文社群&#xff08;ID&#xff1a;javacn666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone死锁&#xff08;Dead Lock&#xff09;指的是两个或两个以上的运算单元&#xff08;进程、线程或协程&#xff09;&#xff0c;都在等待…

【HM】第2课:JavaScript基础

<pre>day02第一天的内容&#xff1a;*html标签里面的表单标签*html标签里面的表格标签思维导图1、JavaScript的简介* 什么是JavaScript&#xff1a;js是一个基于对象和事件驱动的语言&#xff0c;应用客户端。**基于对象&#xff1a;在java里面如果使用对象需要创建&…

你没有见过的 7 种 for 循环优化,超好用!

来源&#xff1a;blog.csdn.net/csdn_aiyang/article/details/75162134我们都经常使用一些循环耗时计算的操作&#xff0c;特别是for循环&#xff0c;它是一种重复计算的操作&#xff0c;如果处理不好&#xff0c;耗时就比较大&#xff0c;如果处理书写得当将大大提高效率&…