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

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

目录

一:内存可见性问题

1:代码解释

2:结果分析

(1)指令拆解

①load

②访问寄存器

(2)指令分析

3:JVM代码优化

4:解决问题

(1)引入.sleep()

(2)volatile

(3)准确描述


一:内存可见性问题

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

java 复制代码
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关键字,是强制性关闭优化,保证每次循环都会从内存中读取数据。开销是变大了,但是数据更准了

(3)JMM模型准确描述

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

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

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

相关推荐
benchmark_cc37 分钟前
用 QuantDash 读取美股行情:AAPL.US 的运行前检查、排错顺序与数据校验清单
python·数据分析·pandas·量化交易·股票数据·quantdash
蜗牛一步步往上爬2 小时前
第十六章 C++语言在深度学习中的应用
开发语言·c++·深度学习
羲云网络4 小时前
GPT Image 2.6 接入指南:Flare、Sunburst 怎么选,编辑失败如何排查
人工智能·python·aigc·图像生成·acedatacloud
张张123y4 小时前
DSH 插件调试避坑:client.js 设置面板、热重载与高频报错排查表
开发语言·javascript·人工智能·开源·ecmascript·后端开发·ai agent
冻柠檬飞冰走茶7 小时前
字母移动游戏题解:贪心求最长可保留连续子串(附 C 实现)
c语言·开发语言·数据结构·算法·动态规划
FRing037 小时前
vue2干货总结7(自定义指令、插槽、单页应用程序、路由的使用(VueRouter))
开发语言·前端·vue.js·vue2
橙橙笔记7 小时前
具身智能的学习 - 完成一个机械臂 第1节
python·学习·机械臂·具身智能
雪落漂泊7 小时前
C++算法 二分查找篇(上)
开发语言·c++·算法
小小张说故事7 小时前
Python 的 class 语句到底执行了什么?拆开 __prepare__、metaclass.__new__ 和那个被讲反的 __set_name__ 顺序
python
波力海苔夹心脆6758 小时前
C# WPF 资源字典详解:ResourceDictionary、MergedDictionaries 与自定义控件案例
开发语言·c#·wpf·入门教程·resource·资源字典