Java内存模型可见性问题相关解析
作者:写代码的木公 发布时间:2023-01-15 06:22:06
这篇文章主要介绍了Java内存模型可见性问题相关解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
前言
之前的文章中讲到,JMM是内存模型规范在Java语言中的体现。JMM保证了在多核CPU多线程编程环境下,对共享变量读写的原子性、可见性和有序性。
本文就具体来讲讲JMM是如何保证共享变量访问的可见性的。
什么是可见性问题
我们从一段简单的代码来看看到底什么是可见性问题。
public class VolatileDemo {
boolean started = false;
public void startSystem(){
System.out.println(Thread.currentThread().getName()+" begin to start system, time:"+System.currentTimeMillis());
started = true;
System.out.println(Thread.currentThread().getName()+" success to start system, time:"+System.currentTimeMillis());
}
public void checkStartes(){
if (started){
System.out.println("system is running, time:"+System.currentTimeMillis());
}else {
System.out.println("system is not running, time:"+System.currentTimeMillis());
}
}
public static void main(String[] args) {
VolatileDemo demo = new VolatileDemo();
Thread startThread = new Thread(new Runnable() {
@Override
public void run() {
demo.startSystem();
}
});
startThread.setName("start-Thread");
Thread checkThread = new Thread(new Runnable() {
@Override
public void run() {
while (true){
demo.checkStartes();
}
}
});
checkThread.setName("check-Thread");
startThread.start();
checkThread.start();
}
}
上面的列子中,一个线程来改变started的状态,另外一个线程不停地来检测started的状态,如果是true就输出系统启动,如果是false就输出系统未启动。那么当start-Thread线程将状态改成true后,check-Thread线程在执行时是否能立即“看到”这个变化呢?答案是不一定能立即看到。这边我做了很多测试,大多数情况下是能“感知”到started这个变量的变化的。但是偶尔会存在感知不到的情况。请看下下面日志记录:
start-Thread begin to start system, time:1577079553515
start-Thread success to start system, time:1577079553516
system is not running, time:1577079553516 ==>此处start-Thread线程已经将状态设置成true,但是check-Thread线程还是没检测到
system is running, time:1577079553516
system is running, time:1577079553516
system is running, time:1577079553516
system is running, time:1577079553516
system is running, time:1577079553516
system is running, time:1577079553516
system is running, time:1577079553517
system is running, time:1577079553517
system is running, time:1577079553517
system is running, time:1577079553517
system is running, time:1577079553517
system is running, time:1577079553517
system is running, time:1577079553517
system is running, time:1577079553519
system is running, time:1577079553519
system is running, time:1577079553519
system is running, time:1577079553519
system is running, time:1577079553519
system is running, time:1577079553519
system is running, time:1577079553519
system is running, time:1577079553519
system is running, time:1577079553519
上面的现象可能会让人比较困惑,为什么有时候check-Thread线程能感知到状态的变化,有时候又感知不到变化呢?这个现象就是在多核CPU多线程编程环境下会出现的可见性问题。
Java内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程在工作内存中保存的值是主内存中值的副本,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存。等到线程对变量操作完毕之后会将变量的最新值刷新回到主内存。
但是何时刷新这个最新值又是随机的。所以就有可能一个线程已经将一个共享变量更新了,但是还没刷新回主内存,那么这时其他对这个变量进行读写的线程就看不到这个最新值。这个就是多CPU多线程编程环境下的可见性问题。也是上面代码会出现问题的原因。
JMM对可见性问题的保证
在多CPU多线程编程环境下,对共享变量的读写会出现可见性问题。但是幸好JMM提供了相应的技术手段来帮我们规避这些问题,可以让程序正确运行。JMM针对可见性问题,主要提供了如下手段:
volatile关键字
synchronized关键字
Lock锁
CAS操作(原子操作类)
volatile关键字
使用volatile关键字修饰一个变量可以保证变量的可见性。所以对于上面的代码,我们只需要简单的修改下代码就可以让程序正确运行了。
private volatile boolean started = false;
使用volatile修饰一个共享变量可以达到如下的效果:
一旦线程对这个共享变量的副本做了修改,会立马刷新最新值到主内存中去;
一旦线程对这个共享变量的副本做了修改,其他线程中对这个共享变量拷贝的副本值会失效,其他线程如果需要对这个共享变量进行读写,必须重新从主内存中加载。
那么volatile具体是怎么达到上面两个效果的呢?其实volatile底层使用的是内存屏障来保证可见性的。
内存屏障(英语:Memory barrier),也称内存栅栏,内存栅障,屏障指令等,是一类同步屏障指令,是CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作。大多数现代计算机为了提高性能而采取乱序执行,这使得内存屏障成为必须。
语义上,内存屏障之前的所有写操作都要写入内存;内存屏障之后的读操作都可以获得同步屏障之前的写操作的结果。因此,对于敏感的程序块,写操作之后、读操作之前可以插入内存屏障。
对内存屏障做下简单的总结:
内存屏障是一个指令级别的同步点;
内存屏障之前的写操作都必须立马刷新回主内存;
内存屏障之后的读操作都必须从主内存中读取最新值;
在有内存屏障的地方,会禁止指令重排序,即屏障下面的代码不能跟屏障上面的代码交换执行顺序,即在执行到内存屏障这句指令时,在它前面的操作已经全部完成。
synchronized关键字
使用synchronized代码块或者synchronized方法也可以保证共享变量的可见性。只要如下修改上面的代码,我们就能得到正确的执行结果。
public synchronized void startSystem(){
System.out.println(Thread.currentThread().getName()+" begin to start system, time:"+System.currentTimeMillis());
value = 2;
started = true;
System.out.println(Thread.currentThread().getName()+" success to start system, time:"+System.currentTimeMillis());
}
public synchronized void checkStartes(){
if (started){
System.out.println("system is running, time:"+System.currentTimeMillis());
}else {
System.out.println("system is not running, time:"+System.currentTimeMillis());
}
}
当线程释放锁时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存中。当线程获取锁时,JMM会把该线程对应的本地内存置为无效。从而使得被监视器保护的临界区代码必须从主内存中读取共享变量。我们发现锁具有和volatile一致的内存语义,所以使用synchronized也可以实现共享变量的可见性。
Lock接口
使用Lock相关的实现类也可以保证共享变量的可见性。其实现原理和synchronized的实现原理类似,这边也就不再赘述了。
CAS机制(Atomic类)
使用原子操作类也可以保证共享变量操作的可见性。所以我们只要如下修稿上面的代码就行了。
private AtomicBoolean started = new AtomicBoolean(false);
原子操作类底层使用的是CAS机制。Java中CAS机制每次都会从主内存中获取最新值进行compare,比较一致之后才会将新值set到主内存中去。而且这个整个操作是一个原子操作。所以CAS操作每次拿到的都是主内存中的最新值,每次set的值也会立即写到主内存中。
来源:https://www.cnblogs.com/54chensongxia/p/12084425.html


猜你喜欢
- 我们在shader中对贴图处理时,有时候会有一些比较复杂的运算,比方说三角函数,开方等,一般情况下,如果可以在越上层做运算,性能会越高。C#
- 一、使用嵌入式关系型SQLite数据库存储数据在Android平台上,集成了一个嵌入式关系型数据库——SQLite,SQLite3支持NUL
- 先给大家展示下效果图:不知道大家对效果图感觉怎么样,个人觉还不错,感兴趣的朋友可以参考下实现代码哦。public class ToggleB
- 值传递:(形式参数类型是基本数据类型):方法调用时,实际参数把它的值传递给对应的形式参数,形式参数只是用实际参数的值初始化自己的存储单元内容
- 本文实例为大家分享了unity绘制一条流动弧线的具体代码,供大家参考,具体内容如下最终效果把下面脚本复制,直接拖上脚本,设置两个点(物体)的
- 在Excel中,可对单元格中的字符串设置多种不同样式,通常只需要获取到单元格直接设置样式即可,该方法设置的样式会应用于该单元格中的所有字符。
- Java执行hadoop的基本操作实例代码向HDFS上传本地文件public static void uploadInputFile(Str
- 在C#中,用于存储的结构较多,如:DataTable,DataSet,List,Dictionary,Stack等
- 1、pom.xml文件添加distributionManagement节点。模块项目中如果存在父子项目,且父子项目的jar包都需要上传到 *
- private void button1_Click(object sender, EventArgs e) &nbs
- 本文实例讲述了Android监听手机电话状态与发送邮件通知来电号码的方法。分享给大家供大家参考,具体如下:在android中可以用Phone
- asp.net是没有直接选取文件夹的控件的,我也不知道,如果大家有的话可以一起交流下。后来我想着应该有三种方法:①先将文件夹压缩后上传服务器
- 效果:代码:XmlDocument xml = new XmlDocument(); &nbs
- 企业级项目开发中都会有文件、图片、视频等文件上传并能够访问的场景,对于初学者Demo可能会直接存储在应用服务器上;对于传统项目可能会单独搭建
- 1.spring boot * 默认有:HandlerInterceptorAdapterAbstractHandlerMappingUse
- 本文为大家分享了一个简单的android左滑删除控件,供大家参考,具体内容如下import android.animation.ValueA
- 前言相信大家对Android悬浮窗应该是很熟悉了,比如说腾讯视频、爱奇艺等APP都有悬浮窗功能。在你打游戏的同时还可以看视频,充分利用屏幕空
- 本文实例为大家分享了java批量解析微信dat文件的具体代码,供大家参考,具体内容如下微信图片默认路径:C:\Users\b-eet\Doc
- 本程序通过JFrame实时显示本机摄像头图像,并将图像存储到一个缓冲区,当用户用鼠标点击JFrame中任何区域时,显示抓取图像的简单动画,同
- 官方文档:https://central.sonatype.org/publish/publish-maven/#a-complete-ex