老掉牙的 synchronized 锁优化,一次给你讲清楚!
我们都知道 synchronized 关键字能实现线程安全,但是你知道这背后的原理是什么吗?今天我们就来讲一讲 synchronized 实现线程同步背后的原因,以及相关的锁优化策略吧。
synchronized 背后的原理
synchronized 关键字经过编译之后,会在同步块的前后分别形成 monitorenter 和 monitorexit 这两个字节码指令,这两个字节码只需要一个指明一个要锁定或解锁的对象。如果 Java 程序中指明了对象参数,那么就用这个对象作为锁。
如果没有指定,那么就根据 synchronized 修饰的是实例方法还是类方法,去拿对应的对象实例或 Class 对象来作为锁对象。因此我们可以知道,synchronized 关键字实现线程同步的背后,其实是 Java 虚拟机规范对于 monitorenter 和 monitorexit 的定义。
在 Java 虚拟机规范对 monitorenter 和 monitorexit 的行为描述中,有两点需要特别注意。
- synchronized 同步块对同一条线程是可重入的,也就是不会出现自己把自己锁死的问题。
- 同步块在已进入的线程执行完之前,会阻塞后面其他线程的进入。
synchronized 关键字在 JDK1.6 版本之前,是通过操作系统的 Mutex Lock 来实现同步的。而操作系统的 Mutex Lock 是操作系统级别的方法,需要切换到内核态来执行。这就需要从用户态转换到内核态中,因此我们说 synchronized 同步是重量级的操作。
synchronized 锁优化
在 JDK1.6 版本中,HotSpot 虚拟机开发团队花了很大的精力去实现各种锁优化技术,如:适应性自旋、锁消除、锁粗话、偏向锁、轻量级锁等。其中最重要的是:自旋锁、轻量级锁、偏向锁这三个,我们重点讲这三个锁优化。
自旋锁与自适应自旋
对于重量级的同步操作来说,最大的消耗其实是内核态与用户态的切换。但很多时候,对于共享数据的操作时间可能很短,比内核态切换到用户态这个耗时还短。
于是有人就想:如果有多个线程并发去获取锁的时候,如果能让后面那个请求锁的线程「稍等一下」,不放弃 CPU 的执行时间,看看持有锁的线程是否会很快释放锁。为了让线程等待,我们只需让线程执行一个忙循环(自旋),这项技术就是所谓的自旋锁。 从理论上来看,如果所有线程都很快地获取锁、释放锁,那么自旋锁是可以带来较大的性能提升的。自旋锁在 JDK 1.4.2 中就已经引入,默认自旋 10 次。但自旋锁默认是关闭的,在 JDK 1.6 中才改为默认开启了。
自旋等待虽然避免了线程切换的开销,但还是要占用处理器的时间。如果锁被占用的时间段,自旋等待的效果就会非常好。但如果锁被长时间占用,那么自旋的线程就会白白消耗处理器的资源,从而带来性能上的浪费。
为了解决特殊情况下自旋锁的性能消耗问题,在 JDK1.6 的时候引入了自适应的自旋锁。 自适应意味着自旋时间不再固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者状态决定。如果在同一锁对象上,自旋等待刚刚成功获得过锁,那么虚拟机认为这次自旋也很有可能再次成功,进而允许线程自旋更长时间,例如自旋 100 个循环。
但如果对于某个锁,自旋很少成功获得过。那虚拟机为了避免浪费 CPU 资源,有可能省略掉自旋过程。有了自旋锁,随着程序运行和性能监控信息的不断完善,虚拟机对锁的状态预测就越准,虚拟机也会变得越来越聪明。
轻量级锁
轻量级锁是 JDK1.6 加入的新型锁机制,名字中的「轻量级」是相对于操作系统互斥量这个重量级锁而言的。轻量级锁诞生的原因,是由于对于绝大部分的锁而言,整个同步周期都不存在竞争。如果没有竞争的话,那就没必要使用重量级锁了,于是就诞生了轻量级锁来提高效率。
对于轻量级锁来说,其同步的流程如下:
- 在代码进入同步块的时候,如果此同步对象没有被锁定(锁标志位为 01 状态),那么虚拟机会在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的 Mark Word 拷贝。
- 虚拟机将使用 CAS 操作尝试将对象的 Mark Word 更新为指向 Lock Record 的指针。如果更新动作成功了,那么线程就泳衣了该对象的锁,并且对象 Mark Word 的锁标志位就变成了 00,表示此对象处于轻量级锁定状态。
简单地说,轻量级锁的同步流程可以总结为:使用 CAS 操作,在线程栈帧与锁对象建立双向的指针。
在没有线程竞争的情况下,轻量级锁使用 CAS 自旋操作避免了使用互斥量的开销,提高了效率。但如果存在锁竞争,除了互斥量的开销外,还额外发生了 CAS 操作。因此在有竞争的情况下,轻量级锁会比传统的重量级锁更慢。
偏向锁
偏向锁是 JDK1.6 中引入的一项优化,它的意思是这个锁会偏向于第一个获得它的线程。如果在接下来的执行过程中,该锁没有被其他线程获取,则持有偏向锁的线程将永远不需要再进行同步。 对于偏向锁来说,其同步流程如下所示:
- 假设当前虚拟机启动了偏向锁,那么当锁对象第一次被线程获取的时候,虚拟机将会把对象的锁标志位设置为 01,偏向锁位设置为 1。同时使用 CAS 操作将线程 ID 记录在对象的 MarkWord 之中。如果 CAS 操作成功,那么持有偏向锁的线程进入锁对应的同步块时,虚拟机将不再进行任何同步操作。
- 当有另外一个线程尝试去获取这个锁时,根据锁对象目前是否处于锁定状态,将其恢复到未锁定(01)或轻量级锁定(00)状态。随后的同步操作,就向上面介绍的轻量级锁那样执行。
可以看到偏向锁还是需要做一些 CAS 操作,但是对比起轻量级锁来说,其要设置的内容大大减少了,因此也提高了一些效率。偏向锁可以提高带有同步但无竞争的程序性能。 它同样是一个带有效益权衡(Trade Off)性质的优化,也就是说,它并不一定总是对程序运行有利,如果程序中大多数的锁总是被多个不同的线程访问,那偏向模式就是多余的。
优化后的锁获取流程
经过 JDK1.6 的优化,synchronized 同步机制的流程变成了:
- 首先,synchronized 会尝试使用偏向锁的方式去竞争锁资源,如果能够竞争到偏向锁,表示加锁成功直接返回。
- 如果竞争锁失败,说明当前锁已经偏向了其他线程。需要将锁升级到轻量级锁,在轻量级锁状态下,竞争锁的线程根据自适应自旋次数去尝试抢占锁资源。
- 如果在轻量级锁状态下还是没有竞争到锁,就只能升级到重量级锁。在重量级锁状态下,没有竞争到锁的线程就会被阻塞。处于锁等待状态的线程需要等待获得锁的线程来触发唤醒。
上面的锁获取流程,可以用如下的示意图来表示:
总结
本文首先简单讲解了 synchronized 关键字实现同步的原理,其实是通过 Java 虚拟机规范对于 monitorenter 和 monitorexit 的支持,从而使得 synchronized 能够实现同步。而 synchronized 同步本质上是通过操作系统的 mutex 锁来实现的。由于操作操作系统 mutex 锁太过于消耗资源,因此在 JDK1.6 后 HotSpot 虚拟机做了一系列的锁优化,其中最重要的便是:自旋锁、轻量级锁、偏向锁。这三个锁的诞生原因,以及提升的点如下表所示。
现状 | 锁名称 | 收益 | 使用场景 |
---|---|---|---|
大多数情况下,等待锁的时间比操作系统 mutex 短得多 | 自旋锁 | 减少内核态与用户态切换的开销 | 线程获取锁时间较短的情况 |
大多数情况下,锁同步期间没有线程竞争 | 轻量级锁 | 与自旋锁相比,减少了自旋时间 | 没有线程竞争锁 |
大多数情况下,锁同步期间没有线程竞争 | 偏向锁 | 与轻量级锁相比,减少了多余的对象复制操作 | 没有线程竞争锁 |
从上面表格可以看到,自旋锁、轻量级锁、偏向锁,他们的优化是逐渐深入的。
- 对于重量级锁来说,自旋锁减少了互斥量的内核、用户态切换开销。
- 轻量级锁,是自旋锁再 Java 内存模型里的直接应用,其同样是减少了内核态与用户态的切换开销。
- 偏向锁,相对于轻量级锁来说,减少了多余的对象复制操作,因此效率更高一些。
参考资料
- 深入理解 Java 虚拟机:JVM 高级特性与最佳实践(第 2 版)- 周志明 - 微信读书
- 【Java 面试】为什么引入偏向锁、轻量级锁,介绍下升级流程 - 跟着 Mic 学架构 - 博客园
- 不可不说的 Java “锁” 事 - 美团技术团队
老掉牙的 synchronized 锁优化,一次给你讲清楚!的更多相关文章
- synchronized锁优化
1.自旋锁和自适应自旋锁 sync在JDK1.6之前之所以被称为重量级锁,是因为对于互斥同步的性能来说,影响最大的就是阻塞的实现.挂起线程与恢复线程的操作都需要转入内核态中完成.从用户态转入内核态是比 ...
- synchronized 锁优化
synchronized 在jdk 1.7之前是重量级锁,独占锁,非公平锁.jdk1.7之后,synchronized引入了 偏向锁,自旋锁,轻量级锁,重量级锁 自旋锁 当线程在获取锁的时候,如果发现 ...
- synchronized锁详解
synchronized的意义 解决了Java共享内存模型带来的线程安全问题: 如:两个线程对初始值为 0 的静态变量一个做自增,一个做自减,各做 5000 次,结果是 0 吗?(针对这个问题进行分析 ...
- Java并发编程:synchronized和锁优化
1. 使用方法 synchronized 是 java 中最常用的保证线程安全的方式,synchronized 的作用主要有三方面: 确保线程互斥的访问代码块,同一时刻只有一个方法可以进入到临界区 保 ...
- Synchronized锁性能优化偏向锁轻量级锁升级 多线程中篇(五)
不止一次的提到过,synchronized是Java内置的机制,是JVM层面的,而Lock则是接口,是JDK层面的 尽管最初synchronized的性能效率比较差,但是随着版本的升级,synchro ...
- Java并发编程:Synchronized底层优化(偏向锁、轻量级锁)
Java并发编程系列: Java 并发编程:核心理论 Java并发编程:Synchronized及其实现原理 Java并发编程:Synchronized底层优化(轻量级锁.偏向锁) Java 并发编程 ...
- 015-线程同步-synchronized几种加锁方式、Java对象头和Monitor、Mutex Lock、JDK1.6对synchronized锁的优化实现
一.synchronized概述基本使用 为确保共享变量不会出现并发问题,通常会对修改共享变量的代码块用synchronized加锁,确保同一时刻只有一个线程在修改共享变量,从而避免并发问题. syn ...
- 并发-Synchronized底层优化(偏向锁、轻量级锁)
Synchronized底层优化(偏向锁.轻量级锁) 参考: http://www.cnblogs.com/paddix/p/5405678.html 一.重量级锁 上篇文章中向大家介绍了Synchr ...
- 【转】Java并发编程:Synchronized底层优化(偏向锁、轻量级锁)
一.重量级锁 上篇文章中向大家介绍了Synchronized的用法及其实现的原理.现在我们应该知道,Synchronized是通过对象内部的一个叫做监视器锁(monitor)来实现的.但是监视器锁本 ...
随机推荐
- 2021.05.03 T3 数字
2021.05.03 T3 数字 问题描述 一个数字被称为好数字当他满足下列条件: 1. 它有**2*n**个数位,n是正整数(允许有前导0) 2. 构成它的每个数字都在给定的数字集合S中. 3. 它 ...
- k8s client-go源码分析 informer源码分析(1)-概要分析
k8s informer概述 我们都知道可以使用k8s的Clientset来获取所有的原生资源对象,那么怎么能持续的获取集群的所有资源对象,或监听集群的资源对象数据的变化呢?这里不需要轮询去不断执行L ...
- Django前后端交互&数据验证
一.前端--->后端 1.form表单 <form method="post" action="/test/?a=1&b=2"> {% ...
- Hadoop(二)Hdfs基本操作
HDFS HDFS由大量服务器组成存储集群,将数据进行分片与副本,实现高容错. 而分片最小的单位就是块.默认块的大小是64M. HDFS Cli操作 官网https://hadoop.apache.o ...
- ONNXRuntime学习笔记(三)
接上一篇完成的pytorch模型训练结果,模型结构为ResNet18+fc,参数量约为11M,最终测试集Acc达到94.83%.接下来有分两个部分:导出onnx和使用onnxruntime推理. 一. ...
- 下载并配置pycharm
1.下载(推荐下载社区版) https://www.jetbrains.com/pycharm/download/#section=windows 2.配置代码编写前注释 得到这种效果: 3.设置字体 ...
- C++进阶-1-模板基础(函数模板、类模板)
C++进阶 模板 1.1 函数模板 1 #include<iostream> 2 using namespace std; 3 4 // 模板 5 6 // 模板的简单实例 7 // 要求 ...
- python写一个能变身电光耗子的贪吃蛇
python写一个不同的贪吃蛇 写这篇文章是因为最近课太多,没有精力去挖洞,记录一下学习中的收获,python那么好玩就写一个大一没有完成的贪吃蛇(主要还是跟课程有关o(╥﹏╥)o,课太多好烦) 第一 ...
- 代码审计VauditDemo程序到exp编写
要对一个程序做系统的审计工作,很多人都认为代码审计工作是在我们将CMS安装好之后才开始的,其实不然,在安装的时候审计就已经开始了! 一般安装文件为install.php或install/或includ ...
- 定位、z-index、JavaScript变量和数据类型
溢出属性 # 文本内容超出了标签的最大范围 overflow: hidden; 直接隐藏文本内容 overflow: auto\scroll; 提供滚动条查看 # 溢出实战案例 div { overf ...