java并发编程六 共享模型之内存

文章目录

    • Java 内存模型
    • 可见性
      • 解决方法
    • 有序性
      • 解决方法

Java 内存模型

JMM 即 Java Memory Model,它定义了主存、工作内存抽象概念,底层对应着 CPU 寄存器、缓存、硬件内存、CPU 指令优化等。

JMM 体现在以下几个方面

  • 原子性 - 保证指令不会受到线程上下文切换的影响
  • 可见性 - 保证指令不会受 cpu 缓存的影响
  • 有序性 - 保证指令不会受 cpu 指令并行优化的影响

可见性

退不出的循环
先来看一个现象,main 线程对 run 变量的修改对于 t 线程不可见,导致了 t 线程无法停止:

static boolean run = true;public static void main(String[] args) throws InterruptedException {Thread t = new Thread(()->{while(run){// ....}});t.start();}sleep(1);run = false; // 线程t不会如预想的停下来

为什么呢?分析一下:

  1. 初始状态, t 线程刚开始从主内存读取了 run 的值到工作内存。
    在这里插入图片描述

  2. 因为 t 线程要频繁从主内存中读取 run 的值,JIT 编译器会将 run 的值缓存至自己工作内存中的高速缓存中,减少对主存中 run 的访问,提高效率
    在这里插入图片描述

  3. 1 秒之后,main 线程修改了 run 的值,并同步至主存,而 t 是从自己工作内存中的高速缓存中读取这个变量的值,结果永远是旧值
    在这里插入图片描述

解决方法

volatile(易变关键字)
它可以用来修饰成员变量和静态成员变量,他可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取它的值,线程操作 volatile 变量都是直接操作主存
可见性 vs 原子性
前面例子体现的实际就是可见性,它保证的是在多个线程之间,一个线程对 volatile 变量的修改对另一个线程可见, 不能保证原子性,仅用在一个写线程,多个读线程的情况: 上例从字节码理解是这样的:

getstatic     run   // 线程 t 获取 run true 
getstatic     run   // 线程 t 获取 run true 
getstatic     run   // 线程 t 获取 run true 
getstatic     run   // 线程 t 获取 run true 
putstatic     run  //  线程 main 修改 run 为 false, 仅此一次 
getstatic     run   // 线程 t 获取 run false 

比较一下之前我们将线程安全时举的例子:两个线程一个 i++ 一个 i-- ,只能保证看到最新值,不能解决指令交错

// 假设i的初始值为0 
getstatic     i  // 线程2-获取静态变量i的值 线程内i=0 
getstatic     i  // 线程1-获取静态变量i的值 线程内i=0 
iconst_1         // 线程1-准备常量1 
iadd             // 线程1-自增 线程内i=1 
putstatic     i  // 线程1-将修改后的值存入静态变量i 静态变量i=1 
iconst_1         // 线程2-准备常量1 
isub             // 线程2-自减 线程内i=-1 
putstatic     i  // 线程2-将修改后的值存入静态变量i 静态变量i=-1 

注意 synchronized 语句块既可以保证代码块的原子性,也同时保证代码块内变量的可见性。但缺点是synchronized 是属于重量级操作,性能相对更低如果在前面示例的死循环中加入 System.out.println() 会发现即使不加 volatile 修饰符,线程 t 也能正确看到

有序性

JVM 会在不影响正确性的前提下,可以调整语句的执行顺序,思考下面一段代码

static int i;
static int j;// 在某个线程内执行如下赋值操作
i = ...; 
j = ...; 

可以看到,至于是先执行 i 还是 先执行 j ,对最终的结果不会产生影响。所以,上面代码真正执行时,既可以是

i = ...; 
j = ...;

也可以是

j = ...;
i = ...; 

这种特性称之为『指令重排』,多线程下『指令重排』会影响正确性。为什么要有重排指令这项优化呢?从 CPU执行指令的原理来理解一下吧

  • 原理之指令级并行
    诡异的结果
int num = 0;
boolean ready = false;// 线程1 执行此方法
public void actor1(I_Result r) {if(ready) {r.r1 = num + num;} 
else {r.r1 = 1;}}// 线程2 执行此方法
public void actor2(I_Result r) {        
num = 2;ready = true;    
}

I_Result 是一个对象,有一个属性 r1 用来保存结果,问,可能的结果有几种?
有同学这么分析
情况1:线程1 先执行,这时 ready = false,所以进入 else 分支结果为 1
情况2:线程2 先执行 num = 2,但没来得及执行 ready = true,线程1 执行,还是进入 else 分支,结果为1
情况3:线程2 执行到 ready = true,线程1 执行,这回进入 if 分支,结果为 4(因为 num 已经执行过了)
但我告诉你,结果还有可能是 0 😁😁😁,信不信吧!
这种情况下是:线程2 执行 ready = true,切换到线程1,进入 if 分支,相加为 0,再切回线程2 执行 num = 2
相信很多人已经晕了 😵😵😵
这种现象叫做指令重排,是 JIT 编译器在运行时的一些优化,这个现象需要通过大量测试才能复现:
借助 java 并发压测工具 jcstress https://wiki.openjdk.java.net/display/CodeTools/jcstress

mvn archetype:generate  -DinteractiveMode=false -DarchetypeGroupId=org.openjdk.jcstress - DarchetypeArtifactId=jcstress-java-test-archetype -DarchetypeVersion=0.5 -DgroupId=cn.onenewcode DartifactId=ordering -Dversion=1.0 

创建 maven 项目,提供如下测试类

@JCStressTest@Outcome(id = {"1", "4"}, expect = Expect.ACCEPTABLE, desc = "ok")@Outcome(id = "0", expect = Expect.ACCEPTABLE_INTERESTING, desc = "!!!!")@Statepublic class ConcurrencyTest {int num = 0;boolean ready = false;
@Actorpublic void actor1(I_Result r) {if(ready) {r.r1 = num + num;} 
else {r.r1 = 1;}}@Actorpublic void actor2(I_Result r) {num = 2;ready = true;}}

执行

mvn clean install 
java -jar target/jcstress.jar 

会输出我们感兴趣的结果,摘录其中一次结果:

*** INTERESTING tests Some interesting behaviors observed. This is for the plain curiosity. 2 matching test results. [OK] test.ConcurrencyTest (JVM args: [-XX:-TieredCompilation]) Observed state   Occurrences              Expectation  Interpretation 0         1,729   ACCEPTABLE_INTERESTING  !!!! 1    42,617,915               ACCEPTABLE  ok 4     5,146,627               ACCEPTABLE  ok [OK] test.ConcurrencyTest (JVM args: []) Observed state   Occurrences              Expectation  Interpretation 0         1,652   ACCEPTABLE_INTERESTING  !!!! 1    46,460,657               ACCEPTABLE  ok 4     4,571,072               ACCEPTABLE  ok 

可以看到,出现结果为 0 的情况有 638 次,虽然次数相对很少,但毕竟是出现了。

解决方法

volatile 修饰的变量,可以禁用指令重排

@JCStressTest
@Outcome(id = {"1", "4"}, expect = Expect.ACCEPTABLE, desc = "ok")@Outcome(id = "0", expect = Expect.ACCEPTABLE_INTERESTING, desc = "!!!!")@Statepublic class ConcurrencyTest {int num = 0;volatile boolean ready = false;@Actorpublic void actor1(I_Result r) {if(ready) {r.r1 = num + num;} 
else {r.r1 = 1;}}@Actorpublic void actor2(I_Result r) {num = 2;ready = true;}}

结果为:

*** INTERESTING tests Some interesting behaviors observed. This is for the plain curiosity. 0 matching test results. 
  • 原理之 volatile
    happens-before
    happens-before 规定了对共享变量的写操作对其它线程的读操作可见,它是可见性与有序性的一套规则总结,抛开以下 happens-before 规则,JMM 并不能保证一个线程对共享变量的写,对于其它线程对该共享变量的读可见
  • 线程解锁 m 之前对变量的写,对于接下来对 m 加锁的其它线程对该变量的读可见
static int x;static Object m = new Object();new Thread(()->{synchronized(m) {x = 10;}},"t1").start();new Thread(()->{synchronized(m) {System.out.println(x);}},"t2").start();
  • 线程对 volatile 变量的写,对接下来其它线程对该变量的读可见
volatile static int x;new Thread(()->{x = 10;},"t1").start();new Thread(()->{System.out.println(x);},"t2").start();
  • 线程 start 前对变量的写,对该线程开始后对该变量的读可见
static int x;x = 10;new Thread(()->{System.out.println(x);},"t2").start();
  • 线程结束前对变量的写,对其它线程得知它结束后的读可见(比如其它线程调用 t1.isAlive() 或 t1.join()等待它结束)
static int x;Thread t1 = new Thread(()->{x = 10;},"t1");t1.start();t1.join();System.out.println(x);
  • 线程 t1 打断 t2(interrupt)前对变量的写,对于其他线程得知 t2 被打断后对变量的读可见(通过
    t2.interrupted 或 t2.isInterrupted)
static int x;public static void main(String[] args) {Thread t2 = new Thread(()->{while(true) {if(Thread.currentThread().isInterrupted()) {System.out.println(x);break;}}},"t2");t2.start();new Thread(()->{sleep(1);x = 10;t2.interrupt();},"t1").start();while(!t2.isInterrupted()) {Thread.yield();}System.out.println(x);}
  • 对变量默认值(0,false,null)的写,对其它线程对该变量的读可见
  • 具有传递性,如果 x hb-> y 并且y hb-> z 那么有x hb-> z ,配合 volatile 的防指令重排,有下面的例子
volatile static int x;static int y;new Thread(()->{    
y = 10;x = 20;},"t1").start();new Thread(()->{// x=20 对 t2 可见, 同时 y=10 也对 t2 可见
System.out.println(x); 
},"t2").start();

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

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

相关文章

STM32独立看门狗

时钟频率 40KHZ 看门狗简介 STM32F10xxx 内置两个看门狗,提供了更高的安全性、时间的精确性和使用的灵活性。两个看 门狗设备 ( 独立看门狗和窗口看门狗 ) 可用来检测和解决由软件错误引起的故障;当计数器达到给 定的超时值时,触发一个中…

腾讯云2核4G服务器CVM标准型S5实例5年优惠价格表

腾讯云服务器续费贵所以一次性买3年或5年,腾讯云轻量应用服务器3年价格有优惠,CVM云服务器5年有特价,腾讯云3年轻量和5年云服务器CVM优惠活动入口,3年轻量应用服务器配置可选2核2G4M和2核4G5M带宽,5年CVM云服务器可以选…

陈可之|三峡|《河水不犯井水的游戏》

《河水不犯井水的游戏》 尺寸:130x90cm 陈可之2007年绘 油画《河水不犯井水的游戏》是陈可之先生三峡系列作品之一,巧借古代传说,并具象化为螃蟹、乌龟、长江水,描绘了一幅和谐共生的画面。 画面右侧,蜿蜒宽阔的长江水…

强化学习_06_pytorch-TD3实践(CarRacing-v2)

0、TD3算法原理简介 详见笔者前一篇实践强化学习_06_pytorch-TD3实践(BipedalWalkerHardcore-v3) 1、CarRacing环境观察及调整 Action SpaceBox([-1. 0. 0.], 1.0, (3,), float32)Observation SpaceBox(0, 255, (96, 96, 3), uint8) 动作空间是[-1~1, 0~1, 0~1]&#xff0c…

解决国内大模型痛点的最佳实践方案

1.前言 自AI热潮掀起以来,国内互联网大厂躬身入局,各类机构奋起追赶,创业型企业纷至沓来。业内戏称,一场大模型的“百模大战”已经扩展到“千模大战”。 根据近期中国科学技术信息研究所发布的《中国人工智能大模型地图研究报告…

【2023下算法课设】Gray码的分治构造算法

Gray码是一个长度为2ⁿ的序列,序列中无相同元素,且每个元素都是长度为n位的二进制位串,相邻元素恰好只有1位不同。例如长度为2的格雷码为(000,001,011,010,110,111,101,100),设计分治算法对任意的n值构造相…

iOS设备信息详解

文章目录 ID 体系iOS设备信息详解IDFA介绍特点IDFA新政前世今生获取方式 IDFV介绍获取方式 UUID介绍特点获取方式 UDID介绍获取方式 OpenUDID介绍 Bundle ID介绍分类其他 IP地址介绍获取方式 MAC地址介绍获取方式正常获取MAC地址获取对应Wi-Fi的MAC地址 系统版本获取方式 设备型…

云HIS源码 云HIS解决方案 支持医保功能

云HIS系统重建统一的信息架构体系,重构管理服务流程,重造病人服务环境,向不同类型的医疗机构提供SaaS化HIS服务解决方案。 云HIS作为基于云计算的B/S构架的HIS系统,为基层医疗机构(包括诊所、社区卫生服务中心、乡镇卫…

【贪心算法】专题练习一

欢迎来到Cefler的博客😁 🕌博客主页:那个传说中的man的主页 🏠个人专栏:题目解析 🌎推荐文章:题目大解析(3) 前言 1.什么是贪心算法?——贪婪鼠目寸光 贪心策…

在pyqt5界面中直接设置图标icon,不需要python程序代码!!一步搞定!!

小白轻松玩转pyqt5 1. 第一步:点击mainwindow,然后在windowicon中上传图片即可2. 设置成功总结(对于小白入门pyqt5的一些忠告) 1. 第一步:点击mainwindow,然后在windowicon中上传图片即可 2. 设置成功 总结(对于小白入…

【Java 进阶篇】Jedis 操作 List:Redis中的列表类型

Redis中的列表(List)是一种有序的、可重复的数据类型,支持在列表的两端进行元素的插入和删除操作。Jedis作为Java开发者与Redis交互的工具,提供了丰富的API来操作List类型。本文将深入介绍Jedis如何操作Redis中的List类型数据&…

嵌入式-stm32-用PWM点亮LED实现呼吸灯

一:知识前置 1.1、LED灯怎么才能亮? 答:LED需要低电平才能亮,高电平是灯灭。 1.2、LED灯为什么可以越来越亮,越来越暗? 答:这是用到不同占空比来实现的,控制LED实现呼吸灯&…

陈可之油画|《远古河谷》,古老的三峡

《远古河谷》 尺寸:90x66cm 陈可之2002年绘 《远古河谷》是陈可之先生“白垩纪组画七千万年三峡原生映象”系列作品之一,通过细腻的笔触所呈现的神秘,去体会自然的历史、生命的历史以及人文的历史! 三峡,沉淀了7000多…

AI赋能金融创新:ChatGPT引领量化交易新时代

文章目录 一、引言二、ChatGPT与量化交易的融合三、实践应用:ChatGPT在量化交易中的成功案例四、挑战与前景五、结论《AI时代Python量化交易实战:ChatGPT让量化交易插上翅膀》📚→ [当当](http://product.dangdang.com/29658180.html) | [京东…

web前端 JQuery下拉菜单的案例

浏览器运行结果&#xff1a; JQuery下载&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/17LXZigLQ8yau0toTGj4P_Q?pwd4332 提取码&#xff1a;4332 代码&#xff1a; <!doctype html> <html> <head> <meta charset"UTF-8"><…

WPS复选框里打对号,显示小太阳或粗黑圆圈的问题解决方法

问题描述 WPS是时下最流行的字处理软件之一&#xff0c;是目前唯一可以和微软office办公套件相抗衡的国产软件。然而&#xff0c;在使用WPS的过程中也会出现一些莫名其妙的错误&#xff0c;如利用WPS打开docx文件时&#xff0c;如果文件包含复选框&#xff0c;经常会出…

自定义注解结合Hutool对SpringBoot接口返回数据进行脱敏

首先说到脱敏问题,我相信在座的很多人都需要处理这样的场景,比如前端页面显示的身份证号、地址等敏感信息都需要脱敏处理,而hutool就有这样的一个工具来辅助我们完成对某些字段属性信息的脱敏,hutool没有现成的实现方式,只是借助这个工具帮助我们来具体实现 前言 我们在…

【Vue2+3入门到实战】(4)Vue基础之指令修饰符 、v-bind对样式增强的操作、v-model应用于其他表单元素 详细示例

目录 一、今日学习目标1.指令补充 二、指令修饰符1.什么是指令修饰符&#xff1f;2.按键修饰符3.v-model修饰符4.事件修饰符 三、v-bind对样式控制的增强-操作class1.语法&#xff1a;2.对象语法3.数组语法4.代码练习 四、京东秒杀-tab栏切换导航高亮1.需求&#xff1a;2.准备代…

RHCE9学习指南 第7章 服务管理

刚装好Windows系统时&#xff0c;需要进行一些优化&#xff0c;如下图所示。 右键单击所得菜单&#xff0c;可以看到一些按钮包括重启、停止、启动该服务。这些管理的是这个服务的当前状态。 双击服务名&#xff0c;在启动类型中设置的是系统启动时&#xff0c;这个服务要不要…

git之UGit可视化工具使用

一、下载安装UGit 链接&#xff1a;https://pan.baidu.com/s/1KGJvWkFL91neI6vAxjGAag?pwdsyq1 提取码&#xff1a;syq1 二 、使用SSH进行远程仓库连接 1.生成SSH密钥 由于我们的本地 git仓库和 gitee仓库之间的传输是通过SSH加密的&#xff0c;所以我们需要配置SSH公钥。才…