JMM 指令重排 volatile happens-before

news2024/11/23 17:17:39

在单线程程序中,操作系统会通过编译器优化重排序指令级并行重排序内存系统重排序三个步骤对源代码进行指令重排,提高代码执行的性能。
在这里插入图片描述
但是在多线程情况下,操作系统“盲目” 地进行指令重排可能会导致我们不想看到的问题,如经典双检锁单例模式。那么我们就需要给操作系统定一些规矩或者上一些手段让他不要随意地指令重排。

但是由于操作系统和硬件的差异,内存访问的差异,会造成相同的代码运行在不同的系统上会出现各种问题。为了屏蔽掉各种硬件和操作系统的内存访问差异,以实现让java程序在各种平台下都能达到一致的多线程并发效果,所以有了 Java 内存模型(JMM)。

JMM 其实是一种抽象的概念,并不真实存在,它描述了一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段、静态字段和构成数组对象的元素)的访问方式。

规范一:主内存和工作内存

主内存

主要存储的是Java实例对象,所有线程创建的实例对象都存放在主内存中,不管该实例对象是成员变量还是方法中的局部变量。由于是共享数据区域,多个线程同一个变量进行访问可能会发送线程安全问题。

工作内存

存储了主内存中的变量副本,每个线程不能访问其他线程的工作内存,因此存储在工作内存的数据不存在线程安全问题。在主内存中的实例对象可以被多线程共享,假如两个线程调用了同一个方法,那么两个线程会将要操作的数据拷贝一份到工作内存中,执行完再刷新到主内存。

规范二:数据同步八大原子操作

知道了工作内存和主内存是怎么交互的了,那么具体实现细节是什么呢?JMM 定义了八种操作来完成:

  1. lock(锁定):作用于主内存的变量,把一个变量标记为一个线程独占状态;
  2. unlock(解锁):作用于主内存的变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定;
  3. read(读取):作用于主内存的变量,把一个变量值从主内存传输到线程的工作内存中,以后随后的load工作使用;
  4. load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量;
  5. use(使用):作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎;
  6. assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量;
  7. store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作;
  8. wirte(写入):作用于工作内存的变量,它把store操作从工作内存中的一个变量值传送到主内存的变量中。

如果要把一个变量从主内存中复制到工作内存中,就需要按顺序地执行 read 和 load 操作;
如果把变量从工作内存中同步到主内存中,就需要按顺序地执行 store 和 write 操作。
在这里插入图片描述

规范三:保证多线程操作时具有原子性、可见性、有序性

1、原子性

含义: 一次操作或者多次操作,要么所有的操作全部都得到执行并且不会受到任何因素的干扰而中断,要么都不执行。

解决方案: 使用 synchronized 和 lock 实现原子性,因为 synchronized 和 Lock 能够保证任一时刻只有一个线程访问该代码块。

2、可见性

含义: 当一个线程对共享变量进行了修改,那么另外的线程都是立即可以看到修改后的最新值。

解决方案: 通过 synchronized、volatile 和 lock。

3、有序性

含义: 保证多线程间的语义一致。
解决方案: 通过 synchronized、volatile 和 lock。

规范四:as-if-serial 原则

不管怎么重排序(编译器和处理器为了提高并行度),单线程程序的执行结果不能被改变。

规范五:happens-before 原则

只靠 synchronized 和 volatile 关键字来保证原子性、可见性以及有序性,那么编写并发程序可能会显得十分麻烦,幸运的是,从JDK 5 开始,Java 使用新的 JSR-133 内存模型,提供了 happens-before 原则来辅助保证程序执行的原子性、可见性和有序性的问题,它是判断数据是否存在竞争、线程十分安全的依据。happens-before 原则内容如下:

  1. 程序顺序原则,即在一个线程内必须保证语义串行,也就是说按照代码顺序执行。
  2. 锁规则,解锁(unlock)操作必然发生在后续的同一个锁的加锁(lock)之前,也就是说,如果对于一个锁解锁后,再加锁,那么加锁的动作必须在解锁动作之后(同一个锁)。
  3. volatile规则, volatile变量的写,先发生于读,这保证了volatile变量的可见性,简单理解就是,volatile变量在每次被线程访问时,都强迫从主内存中读该变量的值,而当该变量发生变化时,又会强迫将最新的值刷新到主内存,任何时刻,不同的线程总是能够看到该变量的最新值。
  4. 线程启动规则,线程的 start() 方法先于它的每一个动作,即如果线程A在执行线程B的 start 方法之前修改了共享变量的值,那么当线程B执行start方法时,线程A对共享变量的修改对线程B可见。
  5. 传递性,A先于B,B先于C,那么A必然先于C。
  6. 线程终止原则,线程的所有操作先于线程的终结,Thread.join() 方法的作用是等待当前执行的线程终止。假设在线程B终止之前,修改了共享变量,线程A从线程B的join方法成功返回,线程B对共享变量的修改将对线程A可见。
  7. 线程中断规则,对线程 interrupt() 方法的调用先行发生于被中断线程的代码检查到中断事件的发生,可以通过 Thread.interrupted() 方法检测线程十分中断。
  8. 对象终结规则,对象的构造函数执行,结束先于 finalize() 方法。

volatile

volatile 的两个作用:

  1. 被 volatile 修饰的共享变量对所有线程总是可见的,也就是当一个线程修改了被 volatile 修饰共享变量的值,新值总是可以被其他线程立即得知。
  2. 禁止指令重排序优化。

第一条是如何实现的?

当线程对 volatile 变量进行写操作时,JMM 会在写入这个变量之后插入一个 Store-Barrier(写屏障)指令,这个指令会强制将本地内存中的变量值刷新到主内存中。
在这里插入图片描述

当线程对 volatile 变量进行读操作时,JMM 会插入一个 Load-Barrier(读屏障)指令,这个指令会强制让本地内存中的变量值失效,从而重新从主内存中读取最新的值。
在这里插入图片描述

第二条如何实现的?

在程序执行期间,为了提高性能,编译器和处理器会对指令进行重排序。但涉及到 volatile 变量时,它们必须遵循一定的规则:

  • 写 volatile 变量的操作之前的操作不会被编译器重排序到写操作之后。
  • 读 volatile 变量的操作之后的操作不会被编译器重排序到读操作之前。

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

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

相关文章

Android Tools | 如何使用Draw.io助力Android开发:从UI设计到流程优化

Android Tools | 如何使用Draw.io助力Android开发:从UI设计到流程优化 1. 引言 在Android开发中,视觉化设计与流程管理至关重要。虽然开发工具如Android Studio强大,但它并不适用于所有设计场景。Draw.io是一款免费的在线绘图工具&#xff…

黑马头条第八天实战(上)

D8 1)登录功能需求说明 用户根据用户名和密码登录密码需要手动加盐验证需要返回用户的token和用户信息 2)模块搭建思路步骤 2.1)模块作用 先捋一下之前搭模块干了啥 feign-api 远程调用 自媒体保存时调用远程客户端进行增加文章&#x…

UE5中使用UTexture2D进行纹理绘制

在UE中有时需要在CPU阶段操作像素,生成纹理贴图等,此时可以通过UTexture2D来进行处理,例子如下: 1.CPP部分 首先创建一个蓝图函数库,将UTexture2D的绘制逻辑封装成单个函数: .h: #include &…

文本转语音工具 ChatTTS 使用教程

文章目录 Part.I IntroductionPart.II 一键安装部署Chap.I 下载Chap.II 使用Chap.III 存在的问题 Part.III 手动部署Chap.I 快速使用Chap.II 开发教程 Reference Part.I Introduction ChatTTS (Chat Text To Speech) 是专门为对话场景设计的文本转语音模型,例如LLM…

arcgisPro添加属性域

1、创建一个面要素,结果如下: 2、在【内容】列表中,选中该要素,点击【数据】选项卡,如下: 3、点击【属性域】按钮,如下: 4、点击【新建域】 5、添加一行属性域,如 6、保存…

【生日视频制作】海底石碑雕刻AE模板修改文字软件生成器教程特效素材【AE模板】

生日视频制作教程海底石碑雕刻AE模板修改文字特效广告生成神器素材祝福玩法AE模板工程 AE模板套用改图文教程↓↓: 怎么如何做的【生日视频制作】海底石碑雕刻AE模板修改文字软件生成器教程特效素材【AE模板】 生日视频制作步骤: 下载AE模板 安装AE软件…

python 注册 Nacos

根据项目需要 将python服务也纳入Nacos 中进行统一管理,所以进行python Nacos 项目适配。 记录本此适配过程。 python 安装不在说明。 系统版本:Linux 5.4.18-87.76-generic KYLINOS SMP Thu Aug 31 09:05:44 UTC 2023 aarch64 aarch64 aarch64 GNU/L…

文心一言 VS 讯飞星火 VS chatgpt (345)-- 算法导论23.2 4题

四、假定图中的边权重全部为整数,且在范围 1 ∼ ∣ V ∣ 1 \sim |V| 1∼∣V∣内。在此种情况下,Kruskal算法最快能多快?如果边的权重取值范围在1到某个常数 W W W之间呢?如果要写代码,请用go语言。 文心一言&#xff…

RPY角的具体描述

目录 一、 RPY角度 二、左乘与右乘 三、xyz固定角和zyx欧拉角旋转矩阵等价 四、参考文献 一、 RPY角度 1.1、X-Y-Z固定角[1] 首先将坐标系{B}和一个已知参考坐标系{A}重合。先将{B}绕旋转γ角,在绕旋转β角,在绕旋转α角,每次旋…

做统计(蓝桥杯初级)

系列文章目录 e,新系列没有目录) 文章目录 系列文章目录前言一、个人名片二、描述三、输入输出以及代码示例1.输入输入样例: 2.输出输出样例: 3.代码示例 四、思路总结 前言 今天我们来做《做统计》 一、个人名片 个人主页&…

Flutter-底部选择弹窗(showModalBottomSheet)

前言 现在有个需求,需要用底部弹窗来添加定时的重复。在这里使用原生的showModalBottomSheet来实现 showModalBottomSheet的Props 名称 描述 isScrollControlled全屏还是半屏isDismissible外部是否可以点击,false不可以点击,true可以点击&a…

剪花布条(KPM模板题)

思路&#xff1a;套用KMP模板即可。 #include<bits/stdc.h> using namespace std; #define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0); #define endl \n int ne[200005]; int main() {IOSstring a,b;while(cin >> a){if(a"#") break;cin …

LEAN 类型系统属性 之 算法式相等的非传递性(Algorithm equality is not transitive)注解

由于 subsingleton 使用函数&#xff08;eliminator&#xff09; 的存在&#xff0c;导致算法式相等&#xff08;Algorithm defintional equality&#xff09;的非传递性。 在《定义上相等的非确定性&#xff08;Undecidability of Definitional Equality&#xff09;》 中有&…

[基于 Vue CLI 5 + Vue 3 + Ant Design Vue 4 搭建项目] 10 Ant Design Vue 的注册

1.全局全部注册 这样就可以将 ant design vue 全部组件注册进来 2.全局部分注册 这样就是按需注册了 本次&#xff0c; 我们选择第1种方式&#xff0c;全部注册进来 3.注册全局 css 4.测试一下 在 AboutView.vue 中添加一个 Test 按钮 使用 npm run serve 启动服务 访问 A…

如何通过subprocess在数据采集中执行外部命令 —以微博为例

介绍 在现代网络爬虫开发中&#xff0c;爬虫程序常常需要与外部工具或命令交互&#xff0c;以完成一些特定任务。subprocess 是 Python 提供的强大模块&#xff0c;用于启动和管理外部进程&#xff0c;广泛应用于爬虫技术中。本文将探讨如何通过 subprocess 在爬虫中执行外部命…

k8s 常见问题梳理

1、“cni0” already has an IP address different from 10.244.2.1/24 删除网卡 ifconfig cni0 down ip link delete cni0ip link add cni0 type bridge ip link set dev cni0 up ifconfig cni0 10.244.2.1/24 ifconfig cni0 mtu 1450 up

二.Unity中使用虚拟摇杆来控制角色移动

上一篇中我们完成了不借助第三方插件实现手游的虚拟摇杆&#xff0c;现在借助这个虚拟摇杆来实现控制角色的移动。 虚拟摇杆实际上就给角色输出方向&#xff0c;类似于键盘的WSAD&#xff0c;也是一个二维坐标&#xff0c;也就是(-1,1)的范围&#xff0c;将摇杆的方向进行归一化…

Windows与Linux下 SDL2的第一个窗口程序

Windows效果和Linux效果如下&#xff1a; 下面是代码&#xff1a; #include <stdio.h> #include "SDL.h"int main(int argc, char* argv[]) { // 初始化SDL视频子系统if (SDL_Init(SDL_INIT_VIDEO) ! 0){// 如果初始化失败&#xff0c;打印错误信息printf(&…

HPA自动扩缩容和命名空间资源限制

目录 HPA概念 安装HPA的依赖环境 安装metrics-server 手动扩缩容 自动扩缩容 yaml文件 创建HPA 自动扩容 自动缩容 命名空间资源限制 HPA概念 HPA是针对pod的数量进行自动扩缩容。&#xff08;是针对控制器deployment、replicaset、StatefulSet创建的pod&#xff0…

TS接口、泛型、自定义类型

这里记录下typescript中接口、泛型和自定义类型的使用 接口定义 // 定义一个接口,用来限制Teacher的属性 export interface Teacher {name: string;age: number;gender: string; }export type teacherList Teacher[];// 一个自定义类型 export type Teachers Array<Teach…