Java之Synchronized与锁升级

Synchronized与锁升级

一、概述

在多线程并发编程中 synchronized 一直是元老级角色,很多人都会称呼它为重量级锁。但是,随着 Java SE 1.6 对 synchronized 进行了各种优化之后,有些情况下它就并不那么重了。

image.png

本文详细介绍 Java SE 1.6 中为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁轻量级锁,以及锁的存储结构和升级过程。

二、实现同步的基础

Java 中的每个对象都可以作为锁,具体变现为以下3中形式:

  1. 对于普通同步方法,锁是当前实例对象
  2. 对于静态同步方法,锁是当前类的 Class 对象
  3. 对于同步方法块,锁是 synchronized 括号里配置的对象

一个线程试图访问同步代码块时,必须获取锁,在退出或者抛出异常时,必须释放锁。

三、实现方式

JVM 基于进入和退出 Monitor 对象来实现方法同步和代码块同步,但是两者的实现细节不一样。

  1. 代码块同步:通过使用 monitorentermonitorexit 指令实现的
  2. 同步方法:ACC_SYNCHRONIZED 修饰

monitorenter 指令是在编译后插入到同步代码块的开始位置,而 monitorexit 指令是在编译后插入到同步代码块的结束处或异常处,对于同步方法,个人觉得也是类似的原理,进入方法前添加一个 monitorenter 指令,退出方法后条件一个 monitorexit 指令。

为了证明 JVM 的实现方式,下面通过反编译代码来证明:

public class Demo {public void f1() {synchronized (Demo.class) {System.out.println("Hello World.");}}public synchronized void f2() {System.out.println("Hello World.");}}

编译之后的字节码如下(只摘取了方法的字节码):

public void f1();descriptor: ()Vflags: ACC_PUBLICCode:stack=2, locals=3, args_size=10: ldc           #2                  // class me/snail/base/Demo2: dup3: astore_14: monitorenter5: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;8: ldc           #4                  // String Hello World.10: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V13: aload_114: monitorexit15: goto          2318: astore_219: aload_120: monitorexit21: aload_222: athrow23: returnException table:from    to  target type5    15    18   any18    21    18   anyLineNumberTable:line 6: 0line 7: 5line 8: 13line 9: 23StackMapTable: number_of_entries = 2frame_type = 255 /* full_frame */offset_delta = 18locals = [ class me/snail/base/Demo, class java/lang/Object ]stack = [ class java/lang/Throwable ]frame_type = 250 /* chop */offset_delta = 4public synchronized void f2();descriptor: ()Vflags: ACC_PUBLIC, ACC_SYNCHRONIZEDCode:stack=2, locals=1, args_size=10: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;3: ldc           #4                  // String Hello World.5: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V8: returnLineNumberTable:line 12: 0line 13: 8

先说 f1() 方法,发现其中一个 monitorenter 对应了两个 monitorexit,这是不对的。但是仔细看 #15: goto 语句,直接跳转到了 #23: return 处,再看 #22: athrow 语句发现,原来第二个 monitorexit 是保证同步代码块抛出异常时锁能得到正确的释放而存在的,这就理解了。

综上:发现同步代码块是通过 monitorenter 和 monitorexit 来实现的,同步方法是加了一个 ACC_SYNCHRONIZED 修饰来实现的。

四、优化后synchronized锁的分类

级别从低到高依次是:

  1. 无锁状态
  2. 偏向锁状态
  3. 轻量级锁状态
  4. 重量级锁状态

锁可以升级,但不能降级。即:无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁是单向的。

下面看一下每个锁状态时,对象头中的 MarkWord 这一个字节中的内容是什么。

以32位系统为例:

1、无锁状态
25bit4bit1bit(是否是偏向锁)2bit(锁标志位)
对象的hashCode对象分代年龄001

这里的 hashCode 是 Object#hashCode 或者 System#identityHashCode 计算出来的值,不是用户覆盖产生的 hashCode。

image.png

2、偏向锁状态

偏向锁:单线程竞争,当线程A第一次竞争到锁时,通过修改MarkWord中的偏向线程ID、偏向模式。如果不存在其他线程竞争,那么持有偏向锁的线程将永远不需要进行同步
主要作用:
● 当一段同步代码一直被同一个线程多次访问,由于只有一个线程那么该线程在后续访问时便会自动获得锁
● 同一个老顾客来访,直接老规矩行方便
结论:
● HotSpot的作者经过研究发现,大多数情况下:在多线程情况下,锁不仅不存在多线程竞争还存在由同一个线程多次获得的情况,偏向锁就是在这种情况下出现的,它的出现是为了解决只有一个线程执行同步时提高性能。
● 偏向锁会偏向于第一个访问锁的线程,如果在接下来的运行过程中,该锁没有被其他线程访问,则持有偏向锁的线程将永远不需要出发同步。也即偏向锁在资源在没有竞争情况下消除了同步语句,懒得连CAS操作都不做了,直接提高程序性能。

理论落地:

image.png

技术实现:

image.png

偏向锁JVM命令:

image.png

偏向锁的撤销:
● 当有另外一个线程逐步来竞争锁的时候,就不能再使用偏向锁了,要升级为轻量级锁,使用的是等到竞争出现才释放锁的机制
● 竞争线程尝试CAS更新对象头失败,会等到全局安全点(此时不会执行任何代码)撤销偏向锁,同时检查持有偏向锁的线程是否还在执行:
○ 第一个线程正在执行Synchronized方法(处于同步块),它还没有执行完,其他线程来抢夺,该偏向锁会被取消掉并出现锁升级,此时轻量级锁由原来持有偏向锁的线程持有,继续执行同步代码块,而正在竞争的线程会自动进入自旋等待获得该轻量级锁
○ 第一个线程执行完Synchronized(退出同步块),则将对象头设置为无所状态并撤销偏向锁,重新偏向。

image.png

Java15以后逐步废弃偏向锁,需要手动开启------->维护成本高

3、轻锁

概念:多线程竞争,但是任意时候最多只有一个线程竞争,即不存在锁竞争太激烈的情况,也就没有线程阻塞。

主要作用:有线程来参与锁的竞争,但是获取锁的冲突时间极短---------->本质是自旋锁CAS

image.png

轻量锁的获取:

image.png

image.png

自旋一定程度和次数(Java8 之后是自适应自旋锁------意味着自旋的次数不是固定不变的):

  • 线程如果自旋成功了,那下次自旋的最大次数会增加,因为JVM认为既然上次成功了,那么这一次也大概率会成功
  • 如果很少会自选成功,那么下次会减少自旋的次数甚至不自旋,避免CPU空转

轻量锁和偏向锁的区别:

  • 争夺轻量锁失败时,自旋尝试抢占锁
  • 轻量级锁每次退出同步块都需要释放锁,而偏向锁是在竞争发生时才释放锁
4、重量级锁状态

有大量线程参与锁的竞争,冲突性很高

image.png

五.小总结

锁升级的过程

20200602120540100.jpg

img

  • 锁升级后,hashcode去哪儿了?

image.png

image.png

● 各种锁优缺点、synchronized锁升级和实现原理

image.png

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

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

相关文章

Grafana二进制部署并配置prometheus数据源

1、获取grafna二进制安装包 https://grafana.com/grafana/download?pggraf&plcmtdeploy-box-1 grafana官网下载地址 [rootambari-hadoop1 ~]# cd /opt/module/grafana/ [rootambari-hadoop1 grafana]# pwd /opt/module/grafana2、在安装自己的安装目录执行 wget https:…

Altium Designer(AD24)新工程复用设计文件图文教程及视频演示

🏡《专栏目录》 目录 1,概述2,复用方法一视频演示2.1,创建工程2.2,复用设计文件 3,复用方法二视频演示4,总结 欢迎点击浏览更多高清视频演示 1,概述 本文简述使用AD软件复用设计文件…

驾驶未来:百度Apollo自动驾驶技术的探索与实践(文末赠送apollo周边)

🎬 鸽芷咕:个人主页 🔥 个人专栏:《linux深造日志》《粉丝福利》 ⛺️生活的理想,就是为了理想的生活! ⛳️ 粉丝福利活动 ✅参与方式:通过连接报名观看课程,即可免费获取精美周边 ⛳️活动链接&#xf…

国家开放大学形成性考核 统一资料 参考试题

试卷代号:1174 水工钢筋混凝土结构(本)参考试题 一、选择题(每小题2分,共20分,在所列备选项中,选1项正确的或最好的作为答案,将选项号填入各题的括号中) 1.钢筋混凝土结…

RasaGPT对话系统的工作原理

RasaGPT 结合了 Rasa 和 Langchain 这 2 个开源项目,当超出 Rasa 现有意图(out_of_scope)的时候,就会执行 ActionGPTFallback,本质上就是利用 Langchain 做了一个 RAG,调用 LLM API。RasaGPT 涉及的技术栈比较多而复杂&#xff0c…

博主自制丨免费下载丨免费使用丨仅用于测试

链接:点我立即下载 提取码:0j6h

SpringMVC:整合 SSM 上篇

文章目录 SpringMVC - 03整合 SSM 上篇一、准备工作二、MyBatis 层1. dao 层2. service 层 三、Spring 层四、SpringMVC 层五、执行六、说明 SpringMVC - 03 整合 SSM 上篇 用到的环境: IDEA 2019(JDK 1.8)MySQL 8.0.31Tomcat 8.5.85Maven…

信号与线性系统翻转课堂笔记9——傅里叶变换概念

信号与线性系统翻转课堂笔记9——傅里叶变换 The Flipped Classroom9 of Signals and Linear Systems 对应教材:《信号与线性系统分析(第五版)》高等教育出版社,吴大正著 一、要点 (1,重点)…

Go 泛型之类型参数

Go 泛型之类型参数 文章目录 Go 泛型之类型参数一、Go 的泛型与其他主流编程语言的泛型差异二、返回切片中值最大的元素三、类型参数(type parameters)四、泛型函数3.1 泛型函数的结构3.2 调用泛型函数3.3 泛型函数实例化(instantiation&…

ARM学习(24)Can的高阶认识和错误处理

笔者来聊一下CAN协议帧的认识和错误处理。 1、CAN协议帧认识 CAN 差分信号,是经过CAN收发器转成差分信号的,CAN RX和TX是逻辑电平。CAN的基础知识,可参考笔者这边文章:ARM学习(21)STM32 外设Can的认识与驱…

四. 基于环视Camera的BEV感知算法-BEVDepth

目录 前言0. 简述1. 算法动机&开创性思路2. 主体结构3. 损失函数4. 性能对比总结下载链接参考 前言 自动驾驶之心推出的《国内首个BVE感知全栈系列学习教程》,链接。记录下个人学习笔记,仅供自己参考 本次课程我们来学习下课程第四章——基于环视Cam…

DALL-E 2: Hierarchical Text-Conditional Image Generation with CLIP Latents

DALL-E 2 论文代码李沐讲DALLE 2方法 上图中,虚线的上半部分是CLIP的训练过程,虚线的下半部分描述的DALL-E 2的训练过程。CLIP训练 在训练时,将文本以及对应的图像分别输入到CLIP的文本编码器和图像编码器,然后得到输出的文本特征和图像特征,这两个特征就是一个正样本,该…