内存可见性与指令重排序

文章目录

  • 内存可见性
    • 内存可见性问题代码演示
    • JMM(Java Memory Model)
  • 指令重排序
    • 指令重排序问题代码演示
    • 指令重排序分析
  • volatile关键字
    • volatile 保证内存可见性 & 禁止指令重排序
    • volatile 不保证原子性

在上一节介绍线程安全问题的过程中,提到了产生线程安全的原因主要有

  1. 操作系统的线程随机调度策略
  2. 对共享数据的写操作
  3. 操作不具有原子性
  4. 内存可见性问题
  5. 指令重排序问题

这五点原因中线程的随机调度是由操作系统调度模块具体实现,无法干预,而多个线程对共享数据的写操作,在某些情况下可以通过调整代码结构进行避免。操作的原子性,可以通过加锁来解决,这小节我们主要来看内存可见性和指令重排序是怎样影响到线程安全的.

由于读取内存,相比较读取寄存器是一个非常慢的操作,编编译器为了进一步提高代码执行的效率,会在保持逻辑不变的前提下,调整生产的代码内容,这样的操作在单线程环境中不会有什么问题,但是,在多线程环境下,编译器就可能会误判,内存可见性和指令重排序都是有编译器优化产生的问题

内存可见性

内存可见性问题代码演示

我们先来观察这段代码:

import java.util.Scanner;public class Test6 {public static int isQuit = 0;// 内存可见性问题public static void main(String[] args) {Thread t1 = new Thread(() -> {while (isQuit == 0) {// 什么也不执行}System.out.println("t1 线程执行完毕");});Thread t2 = new Thread(() -> {System.out.println("请输入isQuit:");Scanner scanner = new Scanner(System.in);isQuit = scanner.nextInt();System.out.println("t2线程执行完毕");});// 启动线程t1.start();t2.start();}
}

执行结果~~

请输入isQuit:
1
t2线程执行完毕

可以看到这里,输入1之后线程并没有执行完毕,那么不应该啊,isQuit的值不为0,t1线程应该会退出循环,可是并没有。我们看一张图。
在这里插入图片描述
在这个过程中,我么看看两个线程都做了什么。t1 线程在一直在读取主内存中isQuit的值,由于循环体没有执行任何逻辑,所以这个速度非常之快。t2线程先将isQuit读入工作内存,然后修改值为1后写回主内存。

如果就这样看,那么在isQuit的值被修改后t1线程也应该随之终止。但事实上Java在运行时,编译器发现在大量读取isQuit的值后,发现isQuit的值并没有改变。于是就做出来一种激进的优化(读取内存要比读取寄存器慢得多),不再读取内存,直接从寄存器中取值,这就导致了后续t2线程在我们输入值后,isQuit的值的确是改变了,但是t1线程并没有取读取内存中的isQuit,这就导致了t1线程对isQuit的内存不可见

在单线程中,编译器这样的优化一般是没有问题的,但是在并发场景下,就不得不考虑这样优化后对代码的影响。于是Java提供了volatile关键字,被这个关键字修饰后,编译器将不会进行优化。

JMM(Java Memory Model)

我们先了解一下JMM, Java虚拟机(JVM)规范文档中定义了Java内存模型.。目的是屏蔽掉各种硬件和操作系统的内存访问差异(跨平台),以实现让Java程序在各种平台下都能达到一致的并发效果。

  • 线程之间的共享变量存在 主内存 (Main Memory) - 相当于内存
  • 每一个线程都有自己的 “工作内存” (Work Memory) - 相当于寄存器
  • 当线程要读取一个共享变量的时候, 会先把变量从主内存拷贝到工作内存, 再从工作内存读取数据.
  • 当线程要修改一个共享变量的时候, 也会先修改工作内存中的副本, 再同步回主内存.

指令重排序

     和内存可见性一样,指令重排序也是在一定条件下触发的编译器的”优化“,目的是提高代码效率,编译器在“保持逻辑不发生变化的情况下”,针对指令执行的顺序进行调整,这就是指令重排序。

指令重排序问题代码演示

class SingletonLazy {private static volatile SingletonLazy instance = null;public static SingletonLazy getInstance() {if (instance == null) {synchronized (SingletonLazy.class) {if (instance == null) {instance = new SingletonLazy();}}}return instance;}private SingletonLazy() { }
}public class Demo22 {public static void main(String[] args) {}
}

在这个单例模式(懒汉模式)中,如果是第一次创建实例,那么会涉及到一个new操作。我们简单的将new操作理解为三步:

  1. 申请内存空间
  2. 在内存空间上构造对象
  3. 把内存地址,复制给instance引用

指令重排序分析

在单线程下,先执行指令2,还是先执行指令3都可以,不影响最终的结果,但是在多线程下,就可能会出现问题。假设编译器将new操作的执行顺序优化为了 1 -> 3 -> 2,t1线程进入,创建单例,但是还没构造对象,就已经将空引用返回(锁已经释放),这是如果t2线程进入,instance还是为空此时就可能会创建出多个实例。

解决方案和内存可见性一样,使用volatile关键字,让编译器不要进行优化

volatile关键字

volatile 保证内存可见性 & 禁止指令重排序

代码在写入 volatile 修饰的变量时

  • 改变线程工作内存中volatile变量副本的值
  • 将改变后的副本的值从工作内存刷新到主内存

代码在读取 volatile 修饰的变量时

  • 从主内存中读取volatile变量的最新值到线程的工作内存中
  • 从工作内存中读取volatile变量的副本

    读取内存相较于读取寄存器来说,非常慢,使用volatile修饰虽然强制读写内存,但是保证了代码的正确性,一般来说,不会牺牲正确新来换取效率。

volatile 不保证原子性

volatile 和 synchronized 有着本质的区别. synchronized 能够保证原子性,volatile保证的是内存可见性,禁止指令重排序。volatile只是强制cpu读取内存,但是不会保证操作的原子性(不可分割)。

不管是原子性、内存可见性还是指令重排序,都可能产生线程安全问题,我们在进行并发编程时一定要谨慎!!

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

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

相关文章

2023亚太杯数学建模B题思路 - 玻璃温室中的微气候法规

# 1 赛题 问题B 玻璃温室中的微气候法规 温室作物的产量受到各种气候因素的影响,包括温度、湿度和风速[1]。其中,适 宜的温度和风速是植物生长[2]的关键。为了调节玻璃温室内的温度、风速等气候因素 , 温室的设计通常采用带有温室风扇的通风系统&#x…

实验4.数据全量、增量、比较更新

【实验目的】 1.利用Kettle的“表输入”,“表输入出”,”JavaScript代码”组件,实现数据全量更新。 2.熟练掌握“JavaScript代码”,“表输入”,“表输入出”组件的使用,实现数据全量更新。 【实验原理】 …

二级指针

*代表指针变量。int*为p的类型。故pp第一个*表示pp为指针int** pp,指向p的二级指针。 p中储存a的地址,pp中储存p的地址。 打印,printf中**pp的表示:pp中储存的是p的地址,第一个*解引用地址p表示p的内容,p的…

回归算法优化过程推导

假设存在一个数据集,包含工资、年龄及贷款额度三个维度的数据。我们需要根据这个数据集进行建模,从而在给定工资和年龄的情况下,实现对贷款额度的预测。其中,工资和年龄是模型构建时的两个特征,额度是模型输出的目标值…

SPASS-ARIMA模型

基本概念 在预测中,对于平稳的时间序列,可用自回归移动平均(AutoRegres- sive Moving Average, ARMA)模型及特殊情况的自回归(AutoRegressive, AR)模型、移动平均(Moving Average, MA)模型等来拟合,预测该时间序列的未来值,但在实际的经济预测中,随机数据序列往往…

macos端文件夹快速访问工具 Default Folder X 最新for mac

Default Folder X 是一款实用的工具,提供了许多增强功能和快捷方式,使用户能够更高效地浏览和管理文件。它的快速导航、增强的文件对话框、自定义设置和快捷键等功能,可以大大提升用户的工作效率和文件管理体验。 快速导航和访问:…

2023亚太杯数学建模B题思路分析 - 玻璃温室中的微气候法规

1 赛题 问题B 玻璃温室中的微气候法规 温室作物的产量受到各种气候因素的影响,包括温度、湿度和风速[1]。其中,适 宜的温度和风速是植物生长[2]的关键。为了调节玻璃温室内的温度、风速等气候因素 , 温室的设计通常采用带有温室风扇的通风系统&#xf…

《数学之美》第三版的读书笔记一、主要是马尔可夫假设、隐马尔可夫模型、图论深度/广度、PageRank相关算法、TF-IDF词频算法

1、马尔可夫假设 从19世纪到20世纪初,俄国有个数学家叫马尔可夫他提出了一种方法,假设任意一个词出现的概率只同它前面的词有关。这种假设在数学上称为马尔可夫假设。 2、二元组的相对频度 利用条件概率的公式,某个句子出现的概率等于每一个词出现的条件概率相乘,于是可展…

【计算机网络笔记】路由算法之层次路由

系列文章目录 什么是计算机网络? 什么是网络协议? 计算机网络的结构 数据交换之电路交换 数据交换之报文交换和分组交换 分组交换 vs 电路交换 计算机网络性能(1)——速率、带宽、延迟 计算机网络性能(2)…

STM32_5(中断)

中断系统 中断:在主程序运行过程中,出现了特定的中断触发条件(中断源),使得CPU暂停当前正在运行的程序,转而去处理中断程序,处理完成后又返回原来被暂停的位置继续运行中断优先级:当…

如何用java的虚拟线程连接数据库

我觉得这个很简单 首先确保你idea支持jdk21. 然后把idea编译成的目标字节码设置为21版本的 然后编写代码。 创建虚拟线程的方式有: Runnable runnable () -> {System.out.println("Hello, world!"); };// 创建虚拟线程 Thread virtualThread Thre…

从0开始学习JavaScript--JavaScript迭代器

JavaScript迭代器(Iterator)是一种强大的编程工具,它提供了一种统一的方式来遍历不同数据结构中的元素。本文将深入探讨JavaScript迭代器的基本概念、用法,并通过丰富的示例代码展示其在实际应用中的灵活性和强大功能。 迭代器的…

【计算思维】蓝桥杯STEMA 科技素养考试真题及解析 2

1、兰兰有一些数字卡片,从 1 到 100 的数字都有,她拿出几张数字卡片按照一定顺序摆放。想一想,第 5 张卡片应该是 A、11 B、12 C、13 D、14 答案:C 2、按照下图的规律,阴影部分应该填 A、 B、 C、 D、 答案&am…

2023亚太杯数学建模竞赛(亚太赛)选题建议+初步分析

如下为C君的2023亚太杯数学建模竞赛&#xff08;亚太赛&#xff09;选题建议初步分析&#xff1a; 提示&#xff1a;DS C君认为的难度&#xff1a;C<A<B&#xff0c;开放度&#xff1a;A<B<C。 以下为ABC题选题建议及初步分析&#xff1a; A题&#xff1a;Image…

Vue批量全局处理undefined和null转为““ 空字符串

我们在处理后台返回的信息&#xff0c;有的时候返回的是undefined或者null&#xff0c;这种字符串容易引起用户的误解&#xff0c;所以需要我们把这些字符串处理一下。 如果每个页面都单独处理&#xff0c;那么页面会很冗余&#xff0c;并且后期如果有修改容易遗漏&#xff0c…

二十二、数组(4)

本章概要 随机生成泛型和基本数组 随机生成 我们可以按照 Count.java 的结构创建一个生成随机值的工具&#xff1a; Rand.java import java.util.*; import java.util.function.*;import static com.example.test.ConvertTo.primitive;public interface Rand {int MOD 10_0…

5-1 Java 网络编程

第1关&#xff1a;URL类与InetAddress类 任务描述 本关任务&#xff1a;了解网络编程基础知识。 相关知识 为了完成本关任务&#xff0c;你需要掌握&#xff1a;1.URL&#xff1b;2.InetAddress。 URL 统一资源定位符&#xff08;Uniform Resource Locator&#xff0c;缩…

2023.11.22 homework

七年级数学 五年级数学 也不知道可以教到几年级&#xff0c;估计很快就教不动了。人生啊。

读像火箭科学家一样思考笔记06_初学者之心

1. 专业化是目前流行的趋势 1.1. 通才&#xff08;generalist&#xff09;是指博而不精之人 1.2. 懂得的手艺越多&#xff0c;反而会家徒四壁 1.2.1. 希腊谚语 1.3. 这种态度代价很大&#xff0c;它阻断了不同学科思想的交融 2. 组合游戏 2.1. 某个行业的变革可能始于另一…

Pycharm的程序调试

有如下代码需要进行调试&#xff1a; i 1 while i < 10:print(i)步骤一&#xff1a;设置断点 步骤二&#xff1a;进入调试视图 方式1&#xff1a;右键单击编辑区&#xff1a;点击’Debug模块名’ ​ 方式2&#xff1a;ShiftF9 ​ 方式3&#xff1a;单机工具栏上的调试按钮…