JMM(Java内存模型)

定义

JMM即Java内存模型(Java memory model),在JSR133里指出了JMM是用来定义一个一致的、跨平台的内存模型,是缓存一致性协议,用来定义数据读写的规则。

内存可见性

在Java中,不同线程拥有各自的私有工作内存,当线程需要读取或修改某个变量时,不能直接去操作主内存中的变量,而是需要将这个变量读取到线程的工作内存变量副本中,当该线程修改其变量副本的值后,其它线程并不能立刻读取到新值,需要将修改后的值刷新到主内存中,其它线程才能从主内存读取到修改后的值

指令重排序

在执行程序时为了提高性能,编译器和处理器常常会对指令做重排序,指令重排序使得代码在多线程执行时会出现一些问题。

其中最著名的案例便是在初始化单例时由于可见性重排序导致的错误。

单例模式

案例1
 
public class Singleton {private static Singleton singleton;private Singleton() {}public static Singleton getInstance() {if (singleton == null) {singleton = new Singleton();}return singleton;}
}

以上代码是经典的懒汉式单例实现,但在多线程的情况下,多个线程有可能会同时进入if (singleton == null) ,从而执行了多次singleton = new Singleton(),从而破坏单例。

案例2
 
public class Singleton {private static Singleton singleton;private Singleton() {}public static Singleton getInstance() {if (singleton == null) {synchronized (Singleton.class) {if (singleton == null) {singleton = new Singleton();}}}return singleton;}
}

以上代码在检测到singleton为null后,会在同步块中再次判断,可以保证同一时间只有一个线程可以初始化单例。但仍然存在问题,原因就是Java中singleton = new Singleton()语句并不是一个原子指令,而是由三步组成:

  1. 为对象分配内存
  2. 初始化对象
  3. 将对象的内存地址赋给引用

但是当经过指令重排序后,会变成:

  1. 为对象分配内存
  2. 将对象的内存地址赋给引用(会使得singleton != null)
  3. 初始化对象

所以就存在一种情况,当线程A已经将内存地址赋给引用时,但实例对象并没有完全初始化,同时线程B判断singleton已经不为null,就会导致B线程访问到未初始化的变量从而产生错误。

案例3
 
public class Singleton {private static volatile Singleton singleton;private Singleton() {}public static Singleton getInstance() {if (singleton == null) {synchronized (Singleton.class) {if (singleton == null) {singleton = new Singleton();}}}return singleton;}
}

以上代码对singleton变量添加了volatile修饰,可以阻止局部指令重排序

那么为什么volatile可以保证变量的可见性和阻止指令重排序?

volatile

原理
  1. 规定线程每次修改变量副本后立刻同步到主内存中,用于保证其它线程可以看到自己对变量的修改
  2. 规定线程每次使用变量前,先从主内存中刷新最新的值到工作内存,用于保证能看见其它线程对变量修改的最新值
  3. 为了实现可见性内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障防止指令重排序
注意:
  1. volatile只能保证基本类型变量的内存可见性,对于引用类型,无法保证引用所指向的实际对象内部数据的内存可见性。关于引用变量类型详见:Java的数据类型。

  2. volilate只能保证共享对象的可见性,不能保证原子性:假设两个线程同时在做x++,在线程A修改共享变量从0到1的同时,线程B已经正在使用值为0的变量,所以这时候可见性已经无法发挥作用,线程B将其修改为1,所以最后结果是1而不是2。

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

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

相关文章

(全网最全)微型计算机原理与接口技术第六版课后习题答案-周荷琴,冯焕清-第8章中断和可编程中断控制器8259A-中国科学技术大学出版社

含有“AI:”开头的题目的答案是问chat的,看个乐就行,不一定正确 1。 什么叫中断?中断的主要功能是什么? 答:当CPU正在处某件事情的时候,外部发生的某一事件请求CPU迅速去处理,于是,CPU暂时中止当前工作,转去处理所发生的事件,中…

markdown加载自定义字体

写讲义,如果没有个像样 的字体多少有点难受。 最终的结果是劝退。 一、需要特定的markdown编辑器,我用的vscode 如果使用joplin、gitee的md文件是无法加载、渲染的。 二、 使用vscode想要渲染的话,似乎只能渲染一部分字体文件。下面不多…

Uniapp(uni-app)学习与快速上手教程

Uniapp(uni-app)学习与快速上手教程 1. 简介 Uniapp是一个跨平台的前端框架,允许您使用Vue.js语法开发小程序、H5、安卓和iOS应用。下面是快速上手的步骤。 2. 创建项目 2.1 可视化界面创建 1、打开 HBuilderX,这是一款专为uni…

【VTKExamples::PolyData】第二十四期 InterpolateTerrain

很高兴在雪易的CSDN遇见你 VTK技术爱好者 QQ:870202403 前言 本文分享VTK样例InterpolateTerrain,希望对各位小伙伴有所帮助! 感谢各位小伙伴的点赞+关注,小易会继续努力分享,一起进步! 你的点赞就是我的动力(^U^)ノ~YO 1. InterpolateTerrain 输出: Interp…

问题:路基施工时以下哪种情况可以不用铺筑试验路段。( ) #经验分享#其他#微信

问题:路基施工时以下哪种情况可以不用铺筑试验路段。( ) A.高速公路和一级公路 B.特殊土质地区公路 C.采用新技术、新材料和新工艺的路基 D.三、四级公路 参考答案如图所示

Java:内部类、枚举、泛型以及常用API --黑马笔记

内部类 内部类是类中的五大成分之一(成员变量、方法、构造器、内部类、代码块),如果一个类定义在另一个类的内部,这个类就是内部类。 当一个类的内部,包含一个完整的事物,且这个事物没有必要单独设计时&a…

(1)短距离(<10KM)

文章目录 1.1 Bluetooth 1.2 CUAV PW-Link 1.3 ESP8266 wifi telemetry 1.4 ESP32 wifi telemetry 1.5 FrSky telemetry 1.6 Yaapu双向遥测地面站 1.7 HOTT telemetry 1.8 MSP(MultiWii 串行协议)(4.1 版) 1.9 MSP (version 4.2) 1.10 SiK Radio v1 1.11 SiK Radio …

异地电脑文件夹共享吗?

在现代社会中,人们越来越多地需要在不同的地方使用电脑来处理文件和数据。对于那些在异地工作或旅行的人来说,如何共享和访问自己的文件夹成为一个棘手的问题。幸运的是,有一些方法可以帮助我们在异地共享电脑文件夹,并实现远程访…

Window环境下使用go编译grpc最新教程

网上的grpc教程都或多或少有些老或者有些问题,导致最后执行生成文件时会报很多错。这里给出个人实践出可执行的编译命令与碰到的报错与解决方法。(ps:本文代码按照煎鱼的教程编写:4.2 gRPC Client and Server - 跟煎鱼学 Go (gitbook.io)&…

【芯片设计- RTL 数字逻辑设计入门 15 -- 函数实现数据大小端转换】

文章目录 函数实现数据大小端转换函数语法函数使用的规则Verilog and Testbench综合图VCS 仿真波形 函数实现数据大小端转换 在数字芯片设计中,经常把实现特定功能的模块编写成函数,在需要的时候再在主模块中调用,以提高代码的复用性和提高设…

专业140+总分420+河海大学863信号与系统考研经验电子信息通信与信息技术,真题,大纲,参考书。

今年的成绩出来倍感欣慰,决定考研的时候并没有想到自己可以考出420的分数,通过自己一年来的努力,成功上岸,期中专业课863信号与系统140接近满分(非常感谢信息通信Jenny老师的专业课辅导和平时悉心答疑,不厌…

同步和异步、阻塞与非阻塞

一、同步和异步的概念 首先同步和异步是访问数据的机制 同步:同步一般指主动请求并等待IO操作完成的方式异步:主动请求数据后便可以继续处理其它任务,随后等待IO操作完毕的通知 两者的区别:同步会一行一行执行代码,而…