当前位置: 首页 > news >正文

<JavaEE> volatile关键字 -- 保证内存可见性、禁止指令重排序

目录

一、内存可见性

1.1 Java内存模型(JMM)

1.2 内存可见性演示

二、指令重排序

三、关键字 volatile 


一、内存可见性

1.1 Java内存模型(JMM)

1)什么是Java内存模型(JMM)?
Java内存模型即Java Memory Model,简称JMM。用于屏蔽各种硬件和操作系统的内存访问差异,以实现让Java程序在各平台下都能够达到一致的内存访问效果,即实现“跨平台”。
2)JMM中的“主内存”概念和“工作内存”概念
“主内存”硬件中的内存,在JMM中表述为“主内存”,其中存储了线程间的共享变量等数据。
“工作内存”CPU寄存器和缓存等临时存储区,在JMM中表述为“工作内存”,每个线程都有自己的“工作内存”。
3)线程和“主内存”、“工作内存”的关系

当线程需要读取共享变量时,会从“主内存”拷贝至“工作内存”,再从“工作内存”读取。

当线程需要修改共享变量时,会先修改“工作内存”中的数据副本,再将数据同步回“主内存”。

线程运行中,数据的交互是频繁且持续的,而CPU访问自身寄存器和高速缓存的速度远高于访问内存的速度。

因此,采用频繁与“工作内存”交互、需要时再与“主内存”交互的工作策略,有利于提高运行效率,是编译器优化的方式之一

1.2 内存可见性演示

什么是内存可见性?

内存可见性是指,线程对共享变量值的修改,能否被其他线程及时察觉。

如果一个线程修改了共享变量值,但没有及时写回内存中,导致其他线程无法获得已修改的正确数据,这就被认为出现了线程安全问题。

内存可见性是导致线程不安全的原因之一。

代码演示内存不可见导致线程不安全:

public class Volatile_Demo0 {//有一个共享变量flag,注意该变量没有被 volatile 修饰;public static int flag = 0;public static void main(String[] args) throws InterruptedException {//创建一个线程,线程中当flag为0时,一直循环判断;Thread t = new Thread(()->{while (flag == 0){}});//启动线程;System.out.println("run开始");t.start();//让main线程休眠两秒后,将flag的值改为1;Thread.sleep(2000);flag = 1;//让main线程等待t线程结束;t.join();System.out.println("run结束");}
}//运行结果:
run开始
...程序一直在执行,没有打印“run结束”。
出现了线程安全问题。

上述代码问题分析:

程序无法结束的原因是什么?

根据代码,flag 在线程启动两秒后被改为 1 ,此时 t 线程应该因为跳出 while 循环而执行完毕。

但实际情况却不是这样,t 线程没有结束。

正如上文“线程和‘主内存’、‘工作内存’的关系”中提到的,线程读取共享数据到“工作内存中”,再从“工作内存”读取数据。

所以此时在 t 线程中,参与 while 循环条件判断的 flag ,实际上是一个存储在“工作内存”的 flag 副本。

当 flag 通过另一线程改变值,改变的是“主内存”中的 flag,t 线程并不能察觉。

因此 t 线程无法从 while 循环中跳出并结束。

这就是内存可见性影响线程安全的情况之一。


二、指令重排序

1)什么是指令重排序?

指令重排序是指编译器自动调整原有代码的执行顺序,在保证逻辑不变的前提下,提高程序运行效率。

指令重排序也是编译器优化的方式之一。

2)指令重排序存在什么问题?

指令重排序的前提是“保证逻辑不变”。这一点在单线程环境下较容易判断,但是在多线程环境下,代码复杂程度高,编译器在编译阶段对代码执行效果的判断存在困难。

因此在多线程环境下,代码重排序很容易导致优化后和优化前的逻辑不等价。

图示演示指令重排序可能出现的问题:


三、关键字 volatile 

1)volatile 的作用是什么?
<1>

保证内存可见性。volatile 修饰的变量每次被访问都必须从内存中读取,每次被修改都必须存储到内存中。

<2>禁止指令重排序。volatile 修饰的变量读写操作的相关指令不允许被重排序。
2)内存可见性和指令重排序都是编译器优化,怎么好像都是负作用?

在大部分场景下,编译器优化都能非常优秀的提高程序的运行效率,只是在多线程编程的部分关键代码中,存在线程不安全的风险。

3)volatile 不保证原子性
volatile 和 synchronized 有本质的区别,synchronized 保证原子性,而 volatile 保证的是内存可见性。
4)合理的使用 volatile 关键字

编译器优化就好像一场激烈的风暴,而程序员要做的就是掌控这场风暴,必要时让风暴停一停。

为此,Java 提供了 volatile 关键字供程序员使用。当使用 volatile 关键字时,强制读写内存,禁止指令重排序,程序运行速度变慢,但数据准确性提高,线程变得安全了。

代码演示 volatile 的使用效果,沿用上文“内存可见性演示”中的代码:

public class Volatile_Demo0 {//有一个共享变量flag,注意该变量已经被 volatile 修饰;public volatile static int flag = 0;public static void main(String[] args) throws InterruptedException {//创建一个线程,线程中当flag为0时,一直循环判断;Thread t = new Thread(()->{while (flag == 0){}});//启动线程;System.out.println("run开始");t.start();//让main线程休眠两秒后,将flag的值改为1;Thread.sleep(2000);flag = 1;//让main线程等待t线程结束;t.join();System.out.println("run结束");}
}//运行结果:
run开始
run结束与上文“内存可见性演示”中的代码唯一的不同,就是在共享变量 flag 上,使用了 volatile 进行修饰。
但这次的结果是程序正常执行完毕,证明了 volatile 的作用。

http://www.lryc.cn/news/253847.html

相关文章:

  • docker安装mysql8
  • 消息丢失排查方法?
  • Linux 匿名页反向映射
  • 国内首个农业开源鸿蒙操作系统联合华为正式发布
  • python HTML文件标题解析问题的挑战
  • AIM: Symmetric Primitive for Shorter Signatures with Stronger Security
  • 【 Go语言使用xorm框架操作数据库】
  • DouyinAPI接口系列丨Douyin商品详情数据接口丨Douyin视频详情数据接口
  • 旺店通对接中国南方电网,打破跨系统连接,让数据轻易互通成为现实
  • 简介Kadane算法及相关的普通动态规划
  • 校园教务管理系统
  • 【LeetCode热题100】【双指针】接雨水
  • 软件工程-(可行性分析、需求分析)
  • HuggingFace学习笔记--BitFit高效微调
  • 阅读笔记|A Survey of Large Language Models
  • JSP 设置静态文件资源访问路径
  • 【Pytorch】Visualization of Feature Maps(4)——Saliency Maps
  • java第三十课
  • Scala--2
  • 【SQL SERVER】定时任务
  • MyBatis-Plus学习笔记(无脑cv即可)
  • 【VUE】watch 监听失效
  • python的异常处理批量执行网络设备的巡检命令
  • react native 环境准备
  • PGSQL(PostgreSQL)数据库安装教程
  • 识别和修复网站上损坏链接的最佳实践
  • 使用Navicat连接MySQL出现的一些错误
  • 4G基站BBU、RRU、核心网设备
  • iphone/安卓手机如何使用burp抓包
  • springboot云HIS医院信息综合管理平台源码