Java内存模型和线程安全

news/2023/5/28 7:28:28

Java内存模型和线程安全

  • Java内存模型
    • 引言
    • volatile关键字
    • synchronized关键字
    • Java线程
  • Java线程安全
  • synchronized锁优化
    • 锁优化技巧列举
      • 自旋锁
      • 锁消除
      • 锁粗化
    • 具体实现
      • 轻量级锁
      • 偏向锁

Java内存模型

引言

在这里插入图片描述

对于多核处理器而言,每个核都会有自己单独的高速缓存,又因为这多个处理器共享同一块主内存,为了在并行运行的情况下,包装各个缓存中缓存的结果的一致性,需要引用缓存一致性协议。

注意: 处理器只和自己的高速缓存交换,如果修改了高速缓存中的数据,就需要同步回主内存,并且通过缓存一致性协议让其他核的高速缓存失效。

高速缓存的出现主要是为了解决CPU运算速度和主内存速度不匹配而引入的缓冲模块


在这里插入图片描述

上图是java的内存模型,Java线程的数据读写都只能从工作内存获取,不同线程的工作内存是隔离的、

此处的工作内存主要对应线程私有的虚拟机栈部分,而主内存则对应Java堆中的对象实例数据部分。

同样JVM也必须通过一种一致性协议来保证多个工作内存间的数据一致性问题。


volatile关键字

volatile具有两个作用:

  • volatile关键字修饰的变量对所有线程立马可见
  • 禁止指令重排优化

为什么volatile变量具有上面两个作用呢? 是因为Java内存模型中对volatile变量定义了特殊处理规则:

  • 每次使用volatile变量前都必须从主内存中获取最新结果
  • 每次修改volatile变量后都必须立刻同步到主内存中
  • volatile修饰的变量不会被指令重排序优化

目前还存在两个问题,我们依次来解决一下:

  • 防止指令重排序如何实现的 ?

在这里插入图片描述

对应汇编代码:

在这里插入图片描述

volatile修饰的变量,赋值后,会生成一个lock addl指令,该指令作用是将本处理器的缓存写入内存,该写入通过缓存一致性协议也会使得其他高速缓存失效。

因为lock alld指令把修改同步到内存,那么意味着所有之前的操作都已经执行完成了,这样便形成了指令重排序无法越过内存屏障的效果。

指令重排序只会在多线程情况下存在并发问题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zgJi8zx8-1673796664803)(Java%E5%86%85%E5%AD%98%E6%A8%A1%E5%9E%8B.assets/image-20230115225020488.png)]


  • volatile修饰的变量一定是并发安全的吗?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IqHUWwPD-1673796664803)(C:/Users/zdh/AppData/Roaming/Typora/typora-user-images/image-20230115223112467.png)]

volatile修饰符提供的两个作用并没有体现出其一定是并发安全的,上面的例子也证明了,那么为什么呢?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AoGABo1U-1673796664803)(C:/Users/zdh/AppData/Roaming/Typora/typora-user-images/image-20230115223232246.png)]

一个自增操作在字节码层面就对应字条指令,如果在算上解释执行每条指令需要使用到多行代码,或者编译执行对应多条机器码,那么光一个自增操作底层可能就需要执行十几条c代码或者几十条汇编指令,并且不保证这些c代码和汇编指令的原子执行,因此不是线程安全的。


synchronized关键字

synchronized关键字具有可见性,因为对一个变量执行lock前,必须从主内存获取最新值,对一个变量执行unlock前,必须先把此变量同步回主内存。


Java线程

线程调度方式:

  • 协同式调度: 线程执行时间由线程本身控制
  • 抢占式调度: 线程执行时间由系统进行分配

java线程采用1:1内核线程方式实现,因此采用的是抢占式调度。

状态转换:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sp6IrVr0-1673796664804)(Java%E5%86%85%E5%AD%98%E6%A8%A1%E5%9E%8B.assets/image-20230115232325626.png)]


Java线程安全

  • 不可变对象一定是线程安全的,如: String,Integer等
  • synchronized关键字实现互斥同步: 通过monitorEnter和monitorExit两个字节码指令完成抢锁和释放锁步骤,释放锁由JVM保证一定能够完成

在这里插入图片描述

  • java的Lock接口,在java层面实现的互斥锁,如: ReentrantLock, 优势: 等待可中断,实现了公平抢锁机制,可以关联多个条件队列。劣势: 需要在finally块中手动释放锁。
  • 如果要实现非阻塞同步,可以使用CAS加重试机制,CAS需要硬件方面提供支持,如: java提供的原子类,AtomicInteger 。CAS会产生ABA问题,可以通过添加版本号解决,如: AtomicStampedReference。
  • 对于不需要任何同步的场景有: 可重入代码(结果可预测),线程本地存储: ThreadLocal

在这里插入图片描述


synchronized锁优化

‘synchronized锁在jdk5之前最大的问题在于一旦抢不到锁就需要阻塞,而不能通过短暂的重试机制来避免阻塞造成的线程切换, 因为java线程是直接映射到内核线程的,因此线程上下文切换开销很大。

锁优化技巧列举

自旋锁

核心思想: 如果抢锁失败,那么通过CAS重新尝试多次,如果成功,那么就避免了阻塞造成的线程上下文切换,如果失败,那么就进行阻塞等待。

JDK 1.4.2中引入了最简单的自旋锁实现,说它简单是因为它的自旋次数默认是十次,不会根据运行状态动态变更。

JDK 6中引入了自适应自旋锁,相比于JDK 1.4.2简单的自旋锁实现而言,对自旋次数进行动态变更。

  • 如果上一次自旋等待过程中成功获得某个对象锁,那么这一次会动态调大自旋次数。
  • 如果多次自旋等待过程中都没能成功获得某个对象锁,那么下一次可能会跳转自旋过程,避免浪费吹起资源。

锁消除

虚拟机即时编译器运行时,对一些代码要求同步,但是通过逃逸分析检测发现当前方法内部所有在堆上的数据都不会逃逸出去,从而被其他线程访问到,那么就可以把它们认为是线程私有的,因此会消除该方法内部所有同步措施。

    public static String concatString(String ... str) {StringBuffer stringBuffer = new StringBuffer();for (String s : str) {stringBuffer.append(s);}return stringBuffer.toString();}

stringBuffer内部每个方法调用都会加锁,但是该方法本身执行是不存在线程安全问题的,因此可以忽略内部所有同步措施。


锁粗化

虽然推荐尽可能将同步范围缩小,但是如果某个方法内部存在下面的情况:

    private static Integer i = 0;private static void display1() {for (int i = 0; i < 10; i++) {synchronized (Main.class) {i++;}}}private static void display2() {synchronized (Main.class) {i++;}synchronized (Main.class) {i++;}synchronized (Main.class) {i++;}}

上面举例的方法中都存在反复对一个对象加锁和解锁的步骤,这样即使在没有线程竞争的情况下,频繁地进行互斥同步操作也会导致性能损耗,因此会进行锁粗化优化:

    private static Integer i = 0;private static void display1() {synchronized (Main.class) {for (int i = 0; i < 10; i++) {i++;}}}private static void display2() {synchronized (Main.class) {i++;i++;i++;}}

把多个同步代码块用一个同步块替换,如果是循环体内部的同步块,那么将同步块外提。


具体实现

synchronized锁最原始的实现如下:
在这里插入图片描述
该实现最大的问题在于忽略了多数同步代码块运行周期内是不存在竞争的,因此频繁的加锁和解锁设置也会导致性能损耗,并且还需要创建一个Monitor实例对象。

轻量级锁

因此,我们也称上面这种原始实现为重量级锁,为了对重量级锁进行优化,jvm推出了轻量级锁:

  • 轻量级锁的核心思想是在线程获取锁时只是简单标记一下锁被当前线程获取,而在释放锁时,再将标记移除
  • 如果当前线程持有轻量级锁期间出现了锁竞争情况,那么轻量级锁会退化为重量级锁

jvm轻量级锁和偏向锁实现都使用到了对象头,我们来看一下:
在这里插入图片描述
轻量级锁实现如下:

在代码即将进入同步块的时候,如果此同步对象没有被锁定(锁标志位为“01”状态),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝(官方为这份拷贝加了一个Displaced前缀,即Displaced Mark Word),这时候线程堆栈与对象头的状态如图所示:
在这里插入图片描述
然后,虚拟机将使用CAS操作尝试把对象的Mark Word更新为指向Lock Record的指针。如果这个更新动作成功了,即代表该线程拥有了这个对象的锁,并且对象Mark Word的锁标志位(Mark Word的最后两个比特)将转变为“00”,表示此对象处于轻量级锁定状态。这时候线程堆栈与对象头的状态如图所示:
在这里插入图片描述
如果这个更新操作失败了,那就意味着至少存在一条线程与当前线程竞争获取该对象的锁。虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,如果是,说明当前线程已经拥有了这个对象的锁,那直接进入同步块继续执行就可以了,否则就说明这个锁对象已经被其他线程抢占了。如果出现两条以上的线程争用同一个锁的情况,那轻量级锁就不再有效,必须要膨胀为重量级锁,锁标志的状态值变为“10”,此时Mark Word中存储的就是指向重量级锁(互斥量)的指针,后面等待锁的线程也必须进入阻塞状态。


偏向锁

轻量级锁可以在没有线程竞争的情况下,避免创建对应的监视器对象,但是如果锁总是被一个线程获取,那么就没有必要在获取锁前打上标记,而释放锁前撤销标记了,可以只打一次标记,如果下次还是这个同一个线程来获取锁,那么就没必要重复进行打标记和释放标记了。

上述锁的实现思路被称为偏向锁,偏的意思就是锁一直没有被其他线程获取,那么持有偏向锁的线程永远不需要再进行同步。

偏向锁原理如下:

当锁对象第一次被线程获取的时候,虚拟机将会把对象头中的标志位设置为“01”、把偏向模式设置为“1”,表示进入偏向模式。同时使用CAS操作把获取到这个锁的线程的ID记录在对象的Mark Word之中。如果CAS操作成功,持有偏向锁的线程以后每次进入这个锁相关的同步块时,虚拟机都可以不再进行任何同步操作(例如加锁、解锁及对Mark Word的更新操作等)。

一旦出现另外一个线程去尝试获取这个锁的情况,偏向模式就马上宣告结束。根据锁对象目前是否处于被锁定的状态决定是否撤销偏向(偏向模式设置为“0”),撤销后标志位恢复到未锁定(标志位为“01”)或轻量级锁定(标志位为“00”)的状态,后续的同步操作就按照上面介绍的轻量级锁那样去执行。偏向锁、轻量级锁的状态转化及对象Mark Word的关系如图所示:

在这里插入图片描述
在Java语言里面一个对象如果计算过哈希码,就应该一直保持该值不变(强烈推荐但不强制,因为用户可以重载hashCode()方法按自己的意愿返回哈希码),否则很多依赖对象哈希码的API都可能存在出错风险。而作为绝大多数对象哈希码来源的Object::hashCode()方法,返回的是对象的一致性哈希码(Identity Hash Code),这个值是能强制保证不变的,它通过在对象头中存储计算结果来保证第一次计算之后,再次调用该方法取到的哈希码值永远不会再发生改变。

因此,当一个对象已经计算过一致性哈希码后,它就再也无法进入偏向锁状态了;而当一个对象当前正处于偏向锁状态,又收到需要计算其一致性哈希码请求时,它的偏向状态会被立即撤销,并且锁会膨胀为重量级锁。

在重量级锁的实现中,对象头指向了重量级锁的位置,代表重量级锁的ObjectMonitor类里有字段可以记录非加锁状态(标志位为“01”)下的Mark WVord,其中自然可以存储原来的哈希码。

偏向锁可以提高带有同步但无竞争的程序性能,但它同样是一个带有效益权衡(Trade Off)性质的优化,也就是说它并非总是对程序运行有利。如果程序中大多数的锁都总是被多个不同的线程访问,那偏向模式就是多余的。在具体问题具体分析的前提下,有时候使用参数-XX:—UseBiasedLocking来禁止偏向锁优化反而可以提升性能。

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

如若内容造成侵权/违法违规/事实不符,请联系郑州代理记账网进行投诉反馈,一经查实,立即删除!

相关文章

高德地图目前是哪个集团下的公司?

其实在2014年02月&#xff0c;阿里巴巴就斥资11亿美元&#xff0c;完成对高德地图的全资收购&#xff0c;所以高德地图目前是属于阿里巴巴集团下的公司了。 在这个路痴众多的时代&#xff0c;高德地图的出现可谓是拯救了一大波路痴者。 人人手机上几乎都安装了高德地图&#…

SNARK原理示例

1. 引言 前序博客有&#xff1a; SNARK DesignRollup项目的SNARK景观 SNARK方案由 Polynomial IOP ➕多项式承诺方案 组成。 当前的Polynomial IOP主要分为三大类&#xff1a; 1&#xff09;基于interactive proofs&#xff08;IPs&#xff09;的Polynomial IOP&#xff1…

win7系统数据库服务器,win7数据库服务器怎么开启

win7数据库服务器怎么开启 内容精选换一换CDC(Change Data Capture)&#xff0c;即数据变更抓取&#xff0c;通过为源端数据源开启CDC&#xff0c;ROMA Connect可实现数据源的实时数据同步以及数据表的物理删除同步。ROMA Connect支持Oracle的XStream和LogMiner两种CDC模式&…

进程概念理解

既然要了解计算机的进程&#xff0c;那么就需要先了解一下计算机的底层结构 目录 冯洛伊曼体系结构 操作系统 系统调用接口 进程 PCB task_struct 内容 操作系统如何组织进程 冯洛伊曼体系结构 想了解计算机的底层结构&#xff0c;那么必定绕不开冯洛伊曼体系结构&…

win7系统iis建立ftp服务器,win7 iis建立ftp服务器

win7 iis建立ftp服务器 内容精选换一换当完成创建外部服务器后&#xff0c;在GaussDB(DWS)数据库中创建一个OBS/HDFS只写外表&#xff0c;用来访问存储在OBS/HDFS上的数据。此外表是只写的&#xff0c;只能用于导出操作。创建外表创建外表的语法格式如下&#xff0c;详细的描述…

1125和855最小公倍数C语言,2016衢州省考行测数量关系送分题:最小公倍数和最大公约数...

二、真题回顾1.如图&#xff0c;街道XYZ在Y处拐弯&#xff0c;XY1125米&#xff0c;YZ855米&#xff0c;在街道一侧等距装路灯&#xff0c;要求X、Y、Z处各装一盏路灯&#xff0c;这条街道最少要安装多少盏路灯?A.47 B.46 C.45 D.44【中公解析】要使X、Y、Z处各装一盏路灯&…

做到这些,再长高10厘米不是梦

标题想长高的盆友就私信我&#xff0c;危害长高的要素有什么&#xff1f;吃的社会学取决于营养均衡&#xff0c;营养成分充足很容易&#xff0c;难的是要确保关键原素的摄取能够 做到是配备。 因此&#xff0c;营养成分篇便是千万不要挑食&#xff0c;提高的关键窍门便是要尤其…

新房装修|厨房台面给我做高了10公分,做饭不方便

真的太想哭了&#xff0c;千算万算&#xff0c;工人竟然把厨房台面做高了&#xff0c;就不知道长点心么&#xff01;而且还不止一点点&#xff0c;足足有10公分&#xff0c;做饭不方便这可咋整啊&#xff0c;以后可是要一直用下去的啊&#xff0c;急急急&#xff01;&#xff0…

kicad最小布线宽度默认是多少_常见停车场管理系统项目的安装布线及注意事项...

现在很多现代化的智能停车场系统都广泛应用于各大小型停车场、地下车库、公寓小区进出口、公司单位的大门口等&#xff0c;智能停车场的主要组成部分是&#xff1a;系统由出口控制机、入口控制机、电动道闸机、车辆检测器、地感、通讯转换器、IC读卡器&#xff0c;停车场管理软…

怎么判断30公分?看我的图文传教就清楚了

想当年&#xff0c;我学车的时候也是糊里糊涂啊&#xff0c;练了一天都没练好&#xff0c;30公分到底是什么鬼&#xff1f;而且不仅是科目二要30公分&#xff0c;科目三也要用到30公分&#xff01;&#xff01;&#xff01;所以说&#xff0c;学会判断30公分&#xff0c;是考车…

75寸的电视机长和宽是多少 75寸电视长宽多少厘米

75寸电视的长和宽&#xff0c;精确为166.03厘米、93.38厘米&#xff0c;大约为166厘米、93厘米。 家里的电视就是活动时8折抢购太给力了 http://www.adiannao.cn/dy 电视的尺寸一般都是代表电视屏幕对角线的长度&#xff0c;所以这里的75寸&#xff0c;其实就是电视屏幕对角线的…

Stream的使用和原理分析

Stream的使用和原理分析1 背景2 基本逻辑原理3 惰性求值4 操作类型5 并行遍历Spliterators6 实现原理初探6.1 代码初步分析6.2 求和的顺序6.3 怎么设计代码按照求和顺序7 源码分析8 Stream idea调试9 原理实现图小结1 背景 Spliterator&#xff08;splitable iterator可分割迭…

Linux常用指令及Web程序的部署

作者&#xff1a;~小明学编程 文章专栏&#xff1a;Linux 格言&#xff1a;热爱编程的&#xff0c;终将被编程所厚爱。 目录 Linux中的常见指令 ls pwd cd 文件操作 touch cat mkdir echo rm cp mv man less vim head tail grep ps netstat Linux权限 搭建Ja…

“华为杯”研究生数学建模竞赛2004年-【华为杯】B题:实用下料的数学模型(附优秀论文)

赛题描述 “下料问题(cutting stock problem)”是把相同形状的一些原材料分割加工成若干个不同规格大小的零件的问题,此类问题在工程技术和工业生产中有着重要和广泛的应用. 这里的“实用下料问题”则是在某企业的实际条件限制下的单一材料的下料问题。 一个好的下料方案首先…

PyCharm 在Mac OS系统中无法进入单元测试

PyCharm 在Mac OS系统中无法进入单元测试模式问题分析解决问题 今天从Clion开发环境切换到PyCharm&#xff0c;准备使用Python版的OpenCV重现一篇Paper的算法模型。由于本人对Python的用法和Pycharm单元测试还不熟悉&#xff0c;在即将进行算法测试的时候&#xff0c;遇到了比较…

嫌自己不够佛系?智能佛珠了解一下

截至本文发布&#xff0c;大概“佛系”这个词还没有完全过时。所以我们就本着“一定要标题党”的原则在标题里借用一下&#xff0c;说实在的&#xff0c;笔者到现在也没搞懂什么是所谓的“佛系”。我们真正想聊的&#xff0c;其实是穿戴设备。穿戴智能是一个研究了许多年的概念…

叁德悟带你认识四大名香

中国香文化形式独特&#xff0c;历史长久&#xff0c;是华夏文化的意味&#xff0c;它与茶文化、插花文化并为中国三大文化现象香文化臻擅长隋唐&#xff0c;鼎盛持续于宋代和明清。 在古书中常提到“沉檀龙麝”&#xff0c;自古以来最名贵的四种香料&#xff0c;分别是“沉香、…

2022-2028年中国沉香产业竞争现状及投资前景分析报告

【报告类型】产业研究 【出版时间】即时更新&#xff08;交付时间约3个工作日&#xff09; 【发布机构】智研瞻产业研究院 【报告格式】PDF版 本报告介绍了沉香行业相关概述、中国沉香行业运行环境、分析了中国沉香行业的现状、中国沉香行业竞争格局、对中国沉香行业做了重…

2022-2028年中国沉香行业发展模式分析及投资趋势预测报告

报告类型&#xff1a;产业研究 报告格式&#xff1a;电子版、纸介版 出品单位&#xff1a;智研咨询-产业信息网 沉香作为传统中药在我国使用悠久&#xff0c;临床上主要用于治疗肺心病、冠心病、脑溢血、胃寒和哮喘等疾病。沉香除了药用价值&#xff0c;还具有很高的收藏价值…

客快物流大数据项目(一百零三):快递追踪需求介绍

文章目录 快递追踪需求介绍 ​​​​​​​前言 背景介绍 快递追踪需求介绍 ​​​​​​​前言