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

JavaEE 第6节 内存可见性问题以及解决方法

目录

一、什么是内存可见性问题?

1、问题代码演示

2、基础知识铺垫

1)硬件层面

2)模型层面(JMM)

二、内存可见性问题的原因

三、volatile解决内存可见性问题


一、什么是内存可见性问题?

1、问题代码演示

public class Threads {static int count = 0;//实现加锁private synchronized static void add() {count++;}public static Object object1=new Object();public static Object object2=new Object();public static int n=0;public static void main(String[] args) throws InterruptedException {Thread thread1=new Thread(()->{while(n==0){//什么都不做}System.out.println("while循环结束");});Runnable runnable=new Runnable() {@Overridepublic void run() {System.out.println("修改n的值为非0");Scanner scanner=new Scanner(System.in);n=scanner.nextInt();System.out.println("修改完成");}};Thread thread2=new Thread(runnable);thread1.start();thread2.start();thread1.join();thread2.join();}}

按照代码逻辑,我们在终端输入一个1,那么就会打印"while循环结束",但实际情况真是如此吗?

请看执行结果:

在输入一个1之后,程序并没有结束(正方形红点),那么就说明这个程序是有bug的。

2、基础知识铺垫

1)硬件层面

想要理解这个bug产生的原因,需要简单了解计算机的存储结构以及它们的特性:

寄存器(Register):访问速度最快,但存储的数据最少,掉电后数据丢失。

高速缓存(Cache):访问速度中等,存储数据中等,掉点后数据丢失。

硬盘(Hard Drive):访问速度最慢,存储数据最多,掉点后数据不会丢失。


Cache和Register都是CPU上的一部分,Cache还可以细分成多级缓存。

Register\Cache\Hard Drive这三者之间访问速度和存储大小差距是非常大的,都是按照数量级进行换算的,这就导致不同的硬件访问数据的速度会有质的不同

2)模型层面(JMM)

Java内存模型(Java Memory Model,JMM)是Java虚拟机(JVM)规范的一部分。它规定了多线程环境下,每个线程访问共享内存的方式,这个模型的主要目标是保证程序员能够在多线程环境下编写出正确高效的程序。

接下来用一张图描述JMM的具体要求:

这里的本地内存指的就是Working Memory,相当于刚才硬件部分介绍的寄存器(Register)以及高速缓存(Cache)。主内存就是Main Memory,相当于刚才硬件部分介绍到的硬盘(Hard Drive)。

在JMM的控制下,线程与线程使用的内存不是物理层面的共享,他们都分别从Main Memory的共享内存区域,拷贝一份副本到自己的Working Memory。

二、内存可见性问题的原因

从刚才的图示中可以知道,想要实现两个线程t1,t2之间的通信需要两步:

1.t1修改了对应变量然后把Working Memory同步更新会Main Memory

2.t2从Main Memory更新Working Memory保证使用的数据是正确的

还记得刚才的代码吗?

t1的while循环中什么都没有写,因此这个空循环的执行速度是非常快的,可能一秒执行几千甚至上万次。这时编译器看不下去了,因为n这个变量是在Main Memory上的(每次的读取速度非常慢),编译器为了优化代码,让t1直接去自己的Working Memory去读取变量n,进而提高代码的执行效率,但这样就会导致内存可见性问题:

三、volatile解决内存可见性问题

volatile的其中一个英文意思是:易变的

也就是说这个关键字修饰的变量,可以告诉编译器不要在Working Memory上读,那里不准确,必须在硬盘上(Main Memory)上读这个数据,因为这个数据很容易改变,一旦改变,Working Memory上有没有及时更新,会产生问题的!

具体用法

public class Threads {static int count = 0;//实现加锁private synchronized static void add() {count++;}public static volatile int n=0;//代码只在这里多加了volatile修饰变量public static void main(String[] args) throws InterruptedException {Thread thread1=new Thread(()->{while(n==0){//什么都不做}System.out.println("while循环结束");});Runnable runnable=new Runnable() {@Overridepublic void run() {System.out.println("修改n的值为非0");Scanner scanner=new Scanner(System.in);n=scanner.nextInt();System.out.println("修改完成");}};Thread thread2=new Thread(runnable);thread1.start();thread2.start();thread1.join();thread2.join();}
}

执行结果:

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

相关文章:

  • es基本操作
  • 开源 AI 智能名片 S2B2C 商城小程序赋能下的社区团购商业模式研究
  • AutoSar AP软件规范中CM介绍及功能概要
  • 【图形学】TA之路-向量
  • [flink]部署模式
  • 为什么不用postman做自动化
  • 一、Matlab基础
  • 执行java -jar命令,显示jar中没有主清单属性
  • 【C++进阶】红黑树
  • linux使用ssh连接一直弹出密码框问题
  • Python 3 数据结构
  • 【开源社区】Elasticsearch(ES)中空值字段 null_value 及通过exists查找非空文档
  • JavaDS —— 位图(BitSet)与 布隆过滤器
  • 如何确保场外个股期权交易的安全?
  • 第2章:LabVIEW FPGA未来发展方向《LabVIEW ZYNQ FPGA宝典》
  • 苹果电脑维护工具:CleanMyMac X让你的Mac焕发新生!
  • MySQL2 DML数据操纵语言和SQL约束
  • Ubuntu 20.04 中安装 Nginx (通过传包编译的方式)、开启关闭防火墙、开放端口号
  • 解决no main manifest attribute错误
  • 002 | 常见的金融量化指标计算
  • Web Vitals:提升用户体验的关键指标
  • c#中的约束、TimeSpan、defult、operator
  • 挖矿木马攻破了服务器
  • 从容应对技术面试:策略、技巧与成功案例
  • Spring Boot 整合 RestTemplate:详解与实战
  • 【利用模板模式和责任链模式实现数据校验】
  • 学习笔记第十九天
  • 设计模式 - 单例模式
  • fastapi之WebSockets
  • Kotlin 和 Java区别