老掉牙的 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)来实现的.但是监视器锁本 ...
随机推荐
- python黑帽子(第三章)
Windows/Linux下包的嗅探 根据os.name判断操作系统 下面是os的源码 posix是Linux nt是Windows 在windows中需要管理员权限.linux中需要root权限 因 ...
- 小程序开发之获取客户来源 scene 场景值 手机设备信息
为什么要获取客户来源 用作数据分析,根据客户来源,做精准转化! 判断客户来源入口方式 1.通过官方的scene场景值 常见场景值 场景值ID 说明 1001 发现栏小程序主入口,「最近使用」列表 10 ...
- 全场景AI推理引擎MindSpore Lite, 助力HMS Core视频编辑服务打造更智能的剪辑体验
移动互联网的发展给人们的社交和娱乐方式带来了很大的改变,以vlog.短视频等为代表的新兴文化样态正受到越来越多人的青睐.同时,随着AI智能.美颜修图等功能在图像视频编辑App中的应用,促使视频编辑效率 ...
- ucore lab7 同步互斥机制 学习笔记
管程的设计实在是精妙,初看的时候觉得非常奇怪,这混乱的进程切换怎么能保证同一时刻只有一个进程访问管程?理清之后大为赞叹,函数中途把前一个进程唤醒后立刻把自己挂起,完美切换.后一个进程又在巧妙的时机将自 ...
- 理解ASP.NET Core - 发送Http请求(HttpClient)
注:本文隶属于<理解ASP.NET Core>系列文章,请查看置顶博客或点击此处查看全文目录 前言 在.NET中,我们有很多发送Http请求的手段,如HttpWebRequest.WebC ...
- Spring Boot 动态修改 log level
引入依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>sp ...
- git 本地项目关联新repo
git initgit remote add origin repo-url git pull origin master --allow-unrelated-histories git add . ...
- 【多线程】线程创建方式三:实现callable接口
线程创建方式三:实现callable接口 代码示例: import org.apache.commons.io.FileUtils; import java.io.File; import java. ...
- [C++STL] vector 容器的入门
vector容器的入门 #include<vector> 创建vector容器的几种方式 数据类型可以是结构体,也能是另外一个容器 vector 的初始化: (1) 创建并声明大小 vec ...
- 虚拟环境与django版本与视图层相关知识
目录 虚拟环境 django版本区别 视图函数返回值 JsonResponse对象 form表单上传文件 request方法 FBV与CBV CBV源码剖析 模板语法传值 传值方式 传值范围 虚拟环境 ...