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

【JavaEE】——内存可见性问题

阿华代码,不是逆风,就是我疯,你们的点赞收藏是我前进最大的动力!!希望本文内容能够帮助到你!

目录

一:内存可见性问题

1:代码解释

2:结果分析

(1)指令拆解

①load

②访问寄存器

(2)指令分析

3:JVM代码优化

4:解决问题

(1)引入.sleep()

(2)volatile

(3)准确描述


一:内存可见性问题

内存可见性引起的多线程安全问题(一个线程读,一个线程写)

package thread;import java.util.Scanner;/*** Created with IntelliJ IDEA.* Description:* User: Hua YY* Date: 2024-09-23* Time: 10:50*/
public class ThreadDemon26 {public static int flag = 0;public static void main(String[] args) {Thread t1 = new Thread(()->{while(flag == 0){//等待t1线程输入flag的值,只要不为0就能结束t1线程}System.out.println("t1线程结束");});Thread t2 = new Thread(()->{System.out.println("请输入flag的值");Scanner scanner = new Scanner(System.in);flag = scanner.nextInt();});t1.start();t2.start();}
}

1:代码解释

这段代码想要表现出来的效果是,t1,t2线程同时运行,通过t2线程中输入的flag的值来控制t1线程是否结束。

例如:t2线程给flag赋值,输入一个1,那么此时t1线程就不会进入while循环,打印t1线程结束。输入0,那t1线程就陷入死循环

2:结果分析

上文我们先后输入了1,0,2......都没能使t1线程结束,这是为什么呢?

(1)指令拆解

while(flag == 0){};

这条语句其实有两个指令

①load

cpu从内存中读取flag的值(load)到cpu的寄存器上(开销很大)

②访问寄存器

cpu访问寄存器中存储的flag的值,与0进行比较(条件跳转指令)(开销低)         

(此处不理解load和为什么开销很大,请看阿华写的前面的文章哈,有详细解释) 

(2)指令分析

重点条件:①中load的操作(读内存),相较于②中访问寄存器的操作,开销大的多。

上述while循环中①②这两条指令整体看,执行的速度非常快,等你scanner几秒钟了,我while循环中①②可能都执行几亿次了(cpu的计算能力非常强)

此时JVM就会怀疑,这个①号load 的操作是否还有存在的必要(节省开销),前几次可能还会load一下,后面发现,反正load 的值都一样(速度太快了,等不到我们scanner输入flag的值),索性就把load这个操作给优化掉,只留一个访问寄存器的操作指令,访问之前寄存器中“缓存”的值,大大提高循环的执行速度。

3:JVM代码优化

在我们编译完代码后,JVM会在保持你代码逻辑不变的前提下,对你写过的代码进行智能分析,并进行优化。

这个保持你代码逻辑不变的条件其实很苛刻,单线程还好,但是遇到多线程就难免会遇到一些bug。

我们上述的代码就是t2修改了内存,但是t1并没有看到,这就叫“内存可见性问题”

4:解决问题

(1)引入.sleep()

治标不治本,加入sleep,load的循环次数减少,JVM优化的迫切程度就会降低

(2)volatile

volatile关键字,是强制性关闭优化,保证每次循环都会从内存中读取数据。开销是变大了,但是数据更准了

功能①:保证内存可见性,每次访问变量都要读取内存,而不是优化到寄存器或者缓存器当中

功能②:禁止指令重排序,对于被volatile修饰的变量的操作指令,是不能被重排序的

(3)JMM模型准确描述

我们的描述:在上述代码中,编译器发现,每次循环都要读取读取内存,开销太大,于是就把读取内存操优化为读取寄存器操作。

JMM模型描述:在上述代码中,编译器发现,每次循环都要读取“主内存”,开销太大,于是就把“主内存”中的数据拷贝到“工作内存”中,后续每次读取都是到“工作内存”中。

注:在JMM模型当中,“主内存”对标内存,“工作内存”对标寄存器+缓存哪一套,之所以这么叫是因为方便跨平台使用。

相关文章:

  • 基于keras 的神经网络股价预测模型
  • 基于springboot+vue医院挂号就诊系统设计与实现
  • C#和数据库高级:虚方法
  • android 页面布局(1)
  • 章管家 listUploadIntelligent.htm SQL注入漏洞
  • matlab r2024a、matlab R2024b保姆级安装教程
  • 语音识别(非实时)
  • Python编程:08- pycharm使用技巧
  • NLP 序列标注任务核心梳理
  • compose remember 作用
  • Vue3+el-menu 递归 emit失效
  • 掌握MATLAB仪器控制工具箱:自动化测试与数据采集的利器
  • python的 __name__和__doc__属性
  • 测温传感器应用
  • 基于TypeScript+React+AntDesign 的车辆车型管理页面
  • 【108天】Java——《Head First Java》笔记(第1-4章)
  • 【mysql】环境安装、服务启动、密码设置
  • 【node学习】协程
  • 2019年如何成为全栈工程师?
  • canvas 高仿 Apple Watch 表盘
  • conda常用的命令
  • Github访问慢解决办法
  • Joomla 2.x, 3.x useful code cheatsheet
  • Python爬虫--- 1.3 BS4库的解析器
  • supervisor 永不挂掉的进程 安装以及使用
  • Synchronized 关键字使用、底层原理、JDK1.6 之后的底层优化以及 和ReenTrantLock 的对比...
  • 等保2.0 | 几维安全发布等保检测、等保加固专版 加速企业等保合规
  • 前端每日实战:61# 视频演示如何用纯 CSS 创作一只咖啡壶
  • 时间复杂度与空间复杂度分析
  • ​字​节​一​面​
  • #{} 和 ${}区别
  • (04)Hive的相关概念——order by 、sort by、distribute by 、cluster by
  • (13)Latex:基于ΤΕΧ的自动排版系统——写论文必备
  • (160)时序收敛--->(10)时序收敛十
  • (八)c52学习之旅-中断实验
  • (十五)、把自己的镜像推送到 DockerHub
  • (原創) 系統分析和系統設計有什麼差別? (OO)
  • (转)C#调用WebService 基础
  • (转)http协议
  • .NET 8 编写 LiteDB vs SQLite 数据库 CRUD 接口性能测试(准备篇)
  • .net core 管理用户机密
  • .net core使用EPPlus设置Excel的页眉和页脚
  • .NET Framework .NET Core与 .NET 的区别
  • .NET 回调、接口回调、 委托
  • .Net 转战 Android 4.4 日常笔记(4)--按钮事件和国际化
  • .net8.0与halcon编程环境构建
  • .net反编译的九款神器
  • .ui文件相关
  • @ConditionalOnProperty注解使用说明
  • [ IOS ] iOS-控制器View的创建和生命周期
  • [ Socket学习 ] 第一章:网络基础知识
  • [04]Web前端进阶—JS伪数组
  • [20171102]视图v$session中process字段含义
  • [Android Studio] 开发Java 程序
  • [Angular] 笔记 6:ngStyle