Synchronized锁升级:无锁-> 偏向锁 -> 轻量级锁 -> 重量级锁
一. 概述
1. Synchronized锁升级的原因
用锁能够实现数据的安全性,但是会带来性能下降。无锁能够基于线程并行提升程序性能,但是会带来安全性下降。
2. Synchronized锁升级的过程
无锁-> 偏向锁 -> 轻量级锁 -> 重量级锁
3. 早期synchronized效率低的原因
Java的线程是映射到操作系统原生线程之上的,如果要阻塞或唤醒一个线程就需要操作系统介入,需要在用户态与核心态之间切换,这种切换会消耗大量的系统资源,因为用户态与内核态都有各自专用的内存空间,专用的寄存器等,用户态切换至内核态需要传递给许多变量、参数给内核,内核也需要保护好用户态在切换时的一些寄存器值、变量等,以便内核态调用结束后切换回用户态继续工作。
在Java早期版本中,synchronized属于重量级,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的Mutex Lock来实现的,挂起线程和恢复线程都需要转入内核态去完成,阻塞或唤醒一个Java线程需要操作系统切换CPU状态来完成,这种状态切换需要耗费处理器时间,如果同步代码块中内容过于简单,这种切换的时间可能比用户代码执行的时间还长,时间成本相对较高。
3.1 Monitor
Monitor可以理解为一种同步工具,也可理解为一种同步机制,常常被描述为一个Java对象。Java对象是天生的Monitor,每一个Java对象都有成为Monitor的潜质,因为在Java的设计中 ,每一个Java对象自打娘胎里出来就带了一把看不见的锁,它叫做内部锁或者Monitor锁。(所以每个对象都能成为一把锁)
Monitor的本质是依赖于底层操作系统的Mutex Lock实现,操作系统实现线程之间的切换需要从用户态到内核态的转换,成本非常高。
3.2 Mutex Lock
Monitor是在jvm底层实现的,底层代码是c++。本质是依赖于底层操作系统的Mutex Lock实现,操作系统实现线程之间的切换需要从用户态到内核态的转换,状态转换需要耗费很多的处理器时间成本非常高。所以synchronized是Java语言中的一个重量级操作。
Monitor与java对象以及线程是如何关联 ?
1.如果一个java对象被某个线程锁住,则该java对象的Mark Word字段中LockWord指向monitor的起始地址
2.Monitor的Owner字段会存放拥有相关联对象锁的线程id
3.3 Synchronized的性能变化
Java5之前,只有重量级锁synchronized,需要用户态和内核态之间的切换。
Java6之后,为了减少获得锁和释放锁所带来的性能消耗,引入了轻量级锁和偏向锁。
4. Synchronized锁升级的策略
Synchronized用的锁是存在Java对象头里的MarkWord中,锁升级功能主要依赖MarkWord中锁标志位和释放偏向锁标志位
二. Synchronized锁种类及升级步骤
引入如下依赖,执行 System.out.println(ClassLayout.parseInstance(o).toPrintable()),可打印Java对象在内存中的内存布局
<!--官网:http://openjdk.java.net/projects/code-tools/jol/定位:分析对象在JVM的大小和分布--><dependency><groupId>org.openjdk.jol</groupId><artifactId>jol-core</artifactId><version>0.9</version></dependency>
1. 总体设计
synchronized在修饰方法和代码块在字节码上实现方式有很大差异,但是内部实现还是基于对象头的MarkWord来实现的,MarkWord64位图如下:
2. 无锁
2.1 MarkWord64位
MarkWord64位如下(倒着看):
00000001 00000000 00000000 00000000
00000000 00000000 00000000 00000000
前25位不使用,接着31位表示hashCode(如何调用则显示具体的哈希值,否则默认为0),接着1位不使用,接着4位表示分代年龄(因不存在GC,故为0),下1位标识偏向锁(0),最后2位是锁标志位(01) 故最后3位001表示无锁
@Testpublic void nl() {Object o = new Object();System.out.println("无锁--------------------------");System.out.println(ClassLayout.parseInstance(o).toPrintable());}
3. 偏向锁
3.1 MarkWord64位
偏向锁在JDK1.6之后是默认开启的,但是启动时间有延迟,所以需要添加参数
-XX:BiasedLockingStartupDelay=0,让其在程序启动时立刻启动。
MarkWord64位如下(倒着看):
00000101 10100000 10001111 00011011
00000000 00000000 00000000 00000000
前54位表示当前线程指针,最后3位101表示偏向锁
@Testpublic void bl(){Object o = new Object();System.out.println(ClassLayout.parseInstance(o).toPrintable());System.out.println("偏向锁--------------------------");//设置参数 -XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0,关闭延时new Thread(()->{synchronized (o){System.out.println(ClassLayout.parseInstance(o).toPrintable());}},"bl").start();}
3.2 偏向锁的作用
当一段同步代码一直被同一个线程多次访问,由于只有一个线程那么该线程在后续访问时便会自动获得锁。多线程的情况下,锁不仅不存在多线程竞争,还存在锁由同一线程多次获得的情况,偏向锁就是在这种情况下出现的,它的出现是为了解决只有在一个线程执行同步时提高性能。
3.3 偏向锁的持有
理论落地:
在实际应用运行过程中发现,“锁总是同一个线程持有,很少发生竞争”,也就是说锁总是被第一个占用他的线程拥有,这个线程就是锁的偏向线程。
那么只需要在锁第一次被拥有的时候,记录下偏向线程ID。这样偏向线程就一直持有着锁(后续这个线程进入和退出这段加了同步锁的代码块时,不需要再次加锁和释放锁。而是直接比较对象头里面是否存储了指向当前线程的偏向锁)。
如果相等表示偏向锁是偏向于当前线程的,就不需要再尝试获得锁了,直到竞争发生才释放锁。以后每次同步,检查锁的偏向线程ID与当前线程ID是否一致,如果一致直接进入同步。无需每次加锁解锁都去CAS更新对象头。如果自始至终使用锁的线程只有一个,很明显偏向锁几乎没有额外开销,性能极高。
假如不一致意味着发生了竞争,锁已经不是总是偏向于同一个线程了,这时候可能需要升级变为轻量级锁,才能保证线程间公平竞争锁。偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程是不会主动释放偏向锁的。
技术实现:
一个synchronized方法被一个线程抢到了锁时,那这个方法所在的对象就会在其所在的Mark Word中将偏向锁修改状态位,同时还会有占用前54位来存储线程指针作为标识。若该线程再次访问同一个synchronized方法时,该线程只需去对象头的Mark Word 中去判断一下是否有偏向锁指向本身的ID,无需再进入 Monitor 去竞争对象了。
3.4 偏向锁的好处
偏向锁的操作不用直接捅到操作系统,不涉及用户到内核转换,不必要直接升级为最高级。JVM不用和操作系统协商设置Mutex(争取内核),它只需要记录下线程ID就标示自己获得了当前锁,不用操作系统介入。在没有其他线程竞争的时候,一直偏向偏心当前线程,当前线程可以一直执行。如果自始至终使用锁的线程只有一个,很明显偏向锁几乎没有额外开销,性能极高。
3.5 偏向锁的撤销
偏向锁使用一种等到竞争出现才释放锁的机制,只有当其他线程竞争锁时,持有偏向锁的原来线程才会被撤销。撤销需要等待全局安全点(该时间点上没有字节码正在执行),同时检查持有偏向锁的线程是否还在执行:
1. 第一个线程正在执行synchronized方法(处于同步块),它还没有执行完,其它线程来抢夺,该偏向锁会被取消掉并出现锁升级。此时轻量级锁由原持有偏向锁的线程持有,继续执行其同步代码,而正在竞争的线程会进入自旋等待获得该轻量级锁
2. 第一个线程执行完成synchronized方法(退出同步块),则将对象头设置成无锁状态并撤销偏向锁,重新偏向。
3.6 代码示例
以卖电影票为例,50张票很夸张的全由线程t1卖出,t2,t3,t4没有卖出一张,就是基于偏向锁
class TrainTicket {private int number = 50;Object objectLock = new Object();public void sale() {synchronized (objectLock) {if (number > 0) {System.out.println(Thread.currentThread().getName() + "卖出倒数第" + (number--) + "张票");}}}
}public class BiasedLockDemo {public static void main(String[] args) {TrainTicket trainTicket = new TrainTicket();new Thread(() -> {for (int i = 0; i < 55; i++) {trainTicket.sale();}}, "t1").start();new Thread(() -> {for (int i = 0; i < 55; i++) {trainTicket.sale();}}, "t2").start();new Thread(() -> {for (int i = 0; i < 55; i++) {trainTicket.sale();}}, "t3").start();new Thread(() -> {for (int i = 0; i < 55; i++) {trainTicket.sale();}}, "t4").start();}
}
4. 轻量级锁
4.1 MarkWord64位
关闭偏向锁之后程序默认会直接进入-------------轻量级锁状态,所以添加参数
-XX:-UseBiasedLocking,关闭偏向锁。
MarkWord64位如下(倒着看):
00000000 11110010 10000010 00011100
00000000 00000000 00000000 00000000
前62位表示指向线程栈中Lock Record的指针,最后2位00表示轻量级锁
public class LightLockDemo {public static void main(String[] args) {Object o = new Object();System.out.println("轻量级锁--------------------------");//设置参数 -XX:-UseBiasedLocking 关闭偏向锁new Thread(()->{synchronized (o){System.out.println(ClassLayout.parseInstance(o).toPrintable());}},"bl").start();}
}
4.2 轻量级锁的作用
轻量级锁是为了在线程近乎交替执行同步块时提高性能。主要目的:在没有多线程竞争的前提下,通过CAS减少重量级锁使用操作系统互斥量产生的性能消耗,也就是先自旋再阻塞。
4.3 轻量级锁的获取
当关闭偏向锁功能或多线程竞争偏向锁会导致偏向锁升级为轻量级锁。
假如线程A已经拿到锁,这时线程B又来抢该对象的锁,由于该对象的锁已经被线程A拿到,当前该锁已是偏向锁了。而线程B在争抢时发现对象头MarkWord中的线程ID不是线程B自己的线程ID(而是线程A),那线程B就会进行CAS操作希望能获得锁。
此时线程B操作中有两种情况:如果锁获取成功,直接替换Mark Word中的线程ID为B自己的ID(A → B),重新偏向于其他线程(即将偏向锁交给其他线程,相当于当前线程"被"释放了锁),该锁会保持偏向锁状态,A线程Over,B线程上位;如果锁获取失败,则偏向锁升级为轻量级锁,此时轻量级锁由原持有偏向锁的线程持有,继续执行其同步代码,而正在竞争的线程B会进入自旋等待获得该轻量级锁。
4.4 轻量级锁的升级
自旋达到一定次数依然没有成功时,升级为重量级锁。JDK6以后,自旋次数是自适应的,根据同一个锁上一次自旋的时间和拥有锁线程的状态来决定。
4.5 轻量锁与偏向锁的区别和不同
轻量级锁每次退出同步块都需要释放锁,而偏向锁是在竞争发生时才释放锁
5. 重量级锁
5.1 MarkWord64位
MarkWord64位如下(倒着看):
00001010 11111111 01111000 00011010
00000000 00000000 00000000 00000000
前62位表示指向互斥量(重量级锁)的指针,最后2位10表示重量级锁
public class HeavyLockDemo {private static Object objectLock = new Object();public static void main(String[] args) {System.out.println("--------------------重量级锁");new Thread(()->{synchronized (objectLock){System.out.println(ClassLayout.parseInstance(objectLock).toPrintable());}},"t1").start();new Thread(()->{synchronized (objectLock){System.out.println(ClassLayout.parseInstance(objectLock).toPrintable());}},"t2").start();new Thread(()->{synchronized (objectLock){System.out.println(ClassLayout.parseInstance(objectLock).toPrintable());}},"t3").start();}
}
5.2 重量级锁的作用
适用于有大量的线程参与锁的竞争,冲突性很高
6. 总结
Synchronized锁升级过程总结:先自旋,不行再阻塞。实际上是把之前的悲观锁(重量级锁)变成在一定条件下使用偏向锁以及使用轻量级(自旋锁CAS)的形式。
Synchronized在修饰方法和代码块在字节码上实现方式有很大差异,但是内部实现还是基于对象头的MarkWord来实现的。JDK1.6之前synchronized使用的是重量级锁,JDK1.6之后进行了优化,拥有了无锁->偏向锁->轻量级锁->重量级锁的升级过程,而不是无论什么情况都使用重量级锁。
偏向锁: 适用于单线程适用的情况,在不存在锁竞争的时候进入同步方法/代码块则使用偏向锁。
轻量级锁:适用于竞争较不激烈的情况(这和乐观锁的使用范围类似), 存在竞争时升级为轻量级锁,轻量级锁采用的是自旋锁,如果同步方法/代码块执行时间很短的话,采用轻量级锁虽然会占用cpu资源但是相对比使用重量级锁还是更高效。
重量级锁:适用于竞争激烈的情况,如果同步方法/代码块执行时间很长,那么使用轻量级锁自旋带来的性能消耗就比使用重量级锁更严重,这时候就需要升级为重量级锁。
锁的优缺点对比
Synchronized锁升级:无锁-> 偏向锁 -> 轻量级锁 -> 重量级锁相关推荐
- 锁升级过程(偏向锁/轻量级锁/重量级锁)
锁的前置知识 如果想要透彻的理解java锁的来龙去脉,需要先了解锁的基础知识:锁的类型.java线程阻塞的代价.Markword. 锁的类型 锁从宏观上分类,分为悲观锁与乐观锁. 乐观锁 乐观锁是一种 ...
- JVM内部锁升级过程(偏向锁,轻量级锁,重量级锁)
目录 对象在内存中是如何布局的 如何查看对象在内存中的布局 markword数据结构 加锁后发生了什么 偏向锁 什么是偏向锁 偏向锁定时hashCode 哪去了? 为什么需要偏向锁 为什么从JDK15 ...
- java中synchronized锁的升级(偏向锁、轻量级锁及重量级锁)
java同步锁前置知识点 编码中如果使用锁可以使用synchronized关键字,对方法.代码块进行同步加锁 Synchronized同步锁是jvm内置的隐式锁(相对Lock,隐式加锁与释放) Syn ...
- 锁升级过程(无锁、偏向锁、轻量级锁、重量级锁)
文章目录 Synchronized锁升级的背景 Synchronized的性能变化 Java5之前,用户态和内核态之间的切换 为什么每个对象都可以称为一把锁? Java6开始优化Synchronize ...
- Synchronized偏向锁、轻量级锁、重量级锁详解
一.偏向锁加锁 1.通过CAS操作将当前线程的地址设置到锁对象的markword中.如果设置成功了,那么就是设置偏向锁成功了 2.在当前线程的栈贞中,创建锁记录(Lock Record),使这个锁记录 ...
- 偏向锁、轻量级锁、重量级锁加锁过程即锁升级膨胀过程
偏向锁.轻量级锁.重量级锁加锁过程即锁升级膨胀过程 synchronized 偏向锁 为什么要引入偏向锁 偏向锁加锁过程 线程获取到锁对象的偏向锁之后,执行完同步代码块之后,会释放这个偏向锁吗 使用了 ...
- java中锁的基本原理和升级:偏向锁、轻量级锁、重量级锁
目录 由一个问题引发的思考 多线程对于共享变量访问带来的安全性问题 线程安全性 思考如何保证线程并行的数据安全性 synchronized 的基本认识 synchronized 的基本语法 synch ...
- 偏向锁、轻量级锁、重量级锁,Synchronized底层源码终极解析!
synchronized是掌握Java高并发的关键知识点,底层源码更是面试重灾区.本文从源码学习synchronized的原理,讲解对象头.偏向锁.轻量级锁.重量级锁等概念. 扫码关注<Java ...
- Java Synchronized 偏向锁/轻量级锁/重量级锁的演变过程
前言 线程并发系列文章: Java 线程基础 Java 线程状态 Java "优雅"地中断线程-实践篇 Java "优雅"地中断线程-原理篇 真正理解Java ...
- java 偏向锁 怎么用_Java锁升级、偏向锁、轻量级锁
偏向锁 当锁对象第一次被线程获取时,虚拟机会把对象头的锁状态标志设置为01(即偏向状态),同时,使用CAS操作把获取到这个锁的线程的ID记录在对象头的mark word中.如果这个CAS操作成功,那么 ...
最新文章
- Oracle 记录插入时“Invalid parameter binding ”错误
- 狼羊菜过河问题深入学习分析——Java语言描述版
- vba 日期加一年_VBA究竟值不值得审计学?
- 硬盘物理序列号修改工具_精品软件:MHDD磁盘坏道扫描工具使用方法图解教程...
- div 隐藏_div的position属性
- eclipse连接MySQL
- 快速锁屏电脑快捷键_电脑小技巧
- 使用Android Studio 进行NDK开发和调试
- Java 算法 旅行家的预算
- Web SCADA 电力接线图工控组态编辑器 1
- vCenter 或者 vmwareworkstation 虚拟机 安装vmware tools
- 虚拟局域网软件开源_用于云和虚拟化的事实上的标准开源软件包括Linux
- 英特尔第十代处理器为什么不支持win7_Intel 第十代CPU(部分U)装WIN7
- python numpy 版本问题:error module compiled against API version 0xc but this version of numpy is 0xb
- P1053 篝火晚会
- 数据分析案例-基于PCA主成分分析法对葡萄酒数据进行分析
- 深度学习入门与快速实践
- 学习数学建模之优化类----蒙特卡洛迭代法(自学)+多元线性回归模型(STATA学习应用)+自己听论文排版的课程2022-01-28
- windows7系统无法开机显示丢失volmgrx.sys驱动文件
- 阿里云域名 ssl免费的到期了
热门文章
- Android 5.x浏览器webView或者qqX5崩溃,Resources$NotFoundException:String resource ID #0x2040003
- 生死看淡,不服就GAN(七)----用更稳定的生成模型WGAN生成cifar
- 【迟到的Java岗面经】面7家,意外收获5家意向offer,越努力越幸运!
- MySQL奇偶数判断
- worldcloud淘宝手机品牌词云分析
- 2020年中国新基建人工智能产业链全景图深度分析汇总(附完整企业名单)
- vue2.0自学教程(一):走进vue2.0大观园
- Arpg战斗系统-技能学习篇
- 【青龙面板】快手JS版脚本
- 微信小程序 之wx.previewImage图片预览(多张图片预览)