Volatile解决内存不可见性

一、多线程下变量的不可见性

在多线程并发执行下,多个线程修改共享的成员变量,会出现一个线程修改了共享变量的值后,另一个线程不能直接 看到该线程修改后的变量的最新值。
我们首先让子线程去更改变量flag的值为true,主线程通过判断后执行。

public class MyThread extends Thread{
    public static void main(String[] args) {
        // 1、启动子线程,将线程中的flag值改为true
        VolatileThread thread = new VolatileThread();
        thread.start();
        // 2、主线程
        while (true){
            if(thread.isFlag()){
                System.out.println("主线程执行,此时flag已经改为true!!!!");
            }
        }
    }
}
class VolatileThread extends Thread{
    private boolean flag = false;
    
    @Override
    public void run() {
        // 线程中修改变量值
        flag = true;
        System.out.println("子线程将flag值变为true");
    }

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}

在这里插入图片描述
验证不可见性,我们加上一个延迟
在这里插入图片描述
在这里插入图片描述此时当子线程将flag变量变成true,但是主线程是看不到的,导致主线程的输出语句并没有输出!

二、变量不可见性内存语意

在介绍多线程并发修改变量不可见现象的原因之前,我们需要了解回顾一下java多线程的内存模型(和java并发编程有关的内存模型)—JMM(注意和JVM的区别)
JMM(java Memory Model):java内存模型,是java虚拟机规范中所定义的一种内存模型,java内存模型是标志化的,屏蔽了不同计算机的底层区别。
java内存模型描述了java程序中各种变量(线程共享变量)的访问规则,以及在jvm中将变量存储到内存和从内存中读取变量这样的底层细节。
JMM有以下规定

  • 所有的共享变量都存储于主内存中,这里说的变量不包含局部变量 ,因为局部变量是私有的,因此不存在竞争问题。
  • 每一个线程还存在自己的工作内存,线程的工作内存,保留了共享变量的副本。
  • 线程对变量的所有操作(读,取)都必须在工作内存中完成,而不能直接读写主内存中的变量。
  • 不同线程间,也不能直接访问对方工作内存中的变量,线程间的变量的值的传递需要通过主内存中转完成。

总结一下:线程读取共享变量到字节的工作内存中,然后更改工作变量副本的值,最后在刷新到主内存当中。
在这里插入图片描述
上述代码的执行流程分析
①:子线程t从主内存读取到数据放入其对应的工作内存。
②:将flag的值更改为true,但是这个时候flag的值还没有写回主内存。(它首先更改自己的工作内存中共享变量副本,什么时候写会内存是不一定的)。
③:此时main方法读取到了flag的值为false。
④:当子线程t将flag的值写回去后,但是main函数里面的while(true)调用的是系统比较底层的代码,速度快,快到没有时间再去读取主存中的值,所以while(true)读取到的值一直是false。 (如果有一个时刻main线程从主内存中读取到了 主内存中flag的最新值,那么if语句就可以执行,但是注意main线程何时从主内存中读取最新的值,我们无法控制)。
在这里插入图片描述
总结:内存不可见性的原因是
每个线程都有自己的工作内存,线程都是从主内存拷贝共享变量的副本值,当一个线程修改了一个共享变量的值时,它可能首先将该值存储在自己的工作内存中,并不会立即写回主内存。其他线程在读取该共享变量时,可能会从自己的工作内存中读取值,而不是从主内存中获取最新值。这种情况下,如果一个线程修改了共享变量的值,其他线程可能无法立即感知到这个变化,导致不可见性问题。

三、变量不可见性解决方案

如何实现多线程间访问共享变量的可见性?

  • 加锁
  • 使用vloatitle关键字
  • while(true)速度快调用本地线程内存,加延时即可访问主内存的共享变量

1.加锁的方式解决

在这里插入图片描述

public class MyThread extends Thread{
    public static void main(String[] args) {
        // 1、启动子线程,将线程中的flag值改为true
        VolatileThread thread = new VolatileThread();
        thread.start();
        // 2、主线程
        while (true){
            synchronized ("1"){   
                if(thread.isFlag()){
                    System.out.println("主线程执行,此时flag已经改为true!!!!");
                }
            }
        }
    }
}
class VolatileThread extends Thread{
    private boolean flag = false;
    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 线程中修改变量值
        flag = true;
        System.out.println("子线程将flag值变为true");
    }
    public boolean isFlag() {
        return flag;
    }
    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}

讲解:当main进入到synchronized代码块中,执行过程如下:
1.线程获取锁
2.清空工作内存(本地内存)
3.从主内存拷贝共享变量最新值到工作内存中称为副本。
4.执行代码,将修改后的副本刷新会主内存中
5.线程释放锁。

2 使用volatile关键字

在这里插入图片描述

public class MyThread extends Thread{
    public static void main(String[] args) {
        // 1、启动子线程,将线程中的flag值改为true
        VolatileThread thread = new VolatileThread();
        thread.start();
        // 2、主线程
        while (true){
            if(thread.isFlag()){
                System.out.println("主线程执行,此时flag已经改为true!!!!");
            }
        }
    }
}
class VolatileThread extends Thread{
    private volatile boolean flag = false;
    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 线程中修改变量值
        flag = true;
        System.out.println("子线程将flag值变为true");
    }

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}

在这里插入图片描述
1.子线程t从主内存读取到数据放入其对应的工作内存.
2.将flag的值更改为true,但是这个时候flag的值还没有写会主内存.
3.此时main方法main方法读取到了flag的值为false.
4.当子线程t将flag的值写回去后,失效其他线程对此变量副本.
5.再次对flag进行操作的时候线程会从主内存读取最新的值,放入到工作内存中.

四、Volatile是如何解决内存不可见性的

在这里插入图片描述
有volatile变量修饰的共享变量进行写操作的时候会多出第二行汇编代码,通过查IA-32架构软件开发者手册可知,Lock前缀的指令在多核处理器下会引发了两件事情。
1)将当前处理器缓存行的数据写回到系统内存。
2)这个写回内存的操作会使在其他CPU里缓存了该内存地址的数据无效(MESI协议)。

解释一下我们的线程是由程序和数据组成的,在CU(控制器)的控制下在ALU(加法器)当中运行,其中我们讲的工作内存指的就是L1和L2级缓存。
在这里插入图片描述
通常为了提高处理速度,处理器不直接和主存进行通信,而是先将系统内存的数据读到内部缓存(L1,L2或寄存器)后再进行操作,但操作完不知道何时会写到内存。这种情况下,如果一个线程修改了共享变量的值,其他线程可能无法立即感知到这个变化,导致不可见性问题。

那么Volatile是如何解决内存不可见性的呢?

lock指令更底层的实现主要有两种方式:总线锁和缓存锁
总线锁:
当其中一个处理器要对共享内存进行操作的时候,在总线上发出 一个LOCK#信号,这个信号就会将总线锁住,使得该 处理器内核可以独占任何共享内存,而其他处理器内核只能等待。在锁定期间,其他处理器内核不能操作其他内存地址的数据,所以总线锁定 的开销比较大,这种机制显然是不合适的。
在这里插入图片描述
缓存锁:
就是指内存区域如果被缓存在处理器的缓存行中,并且在Lock期间被锁定,那么当它执 行锁操作回写到内存时,不再总线上加锁,而是修改内部的内存地址,基于缓存一致性协议来保证操作 的原子性。
缓存一致性协议(MESI),将缓存行中的数据划分成了4个状态:修改、独占、共享、失效

  • M (Modifhed修改) :当cpu对变量进行修改时,现在cpu内的缓存行中上锁,并向总线发信号,此时cpu中的变量状态为M。
  • E (Exclusive独享) :当cpu读取一 个变量时,该变量在工作内存中的状态是E。
  • S (Shared共享) :当cpu读取该变量时,两个cpu中该变量的状态由E转为S。
  • | (Invalid无效) : cpu嗅探到变量被其他cpu修改的信号,于是将自己缓存行中的变量状态设置为i,即失效。则cpu再从内存中获取最新数据。

流程如下
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/627021.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

运维别卷系列 - 云原生监控平台 之 04.prometheus 查询语句 promql 实践

文章目录 [toc]PromQL 简介什么是时间序列 PromQL 数据类型即时向量 Instant vector范围向量 Range vectorTime DurationsOffset modifier modifier 浮点值 Scalar字符串 String PromQL FUNCTIONSfloor()irate()rate()round()sort()sort_desc() PromQL 运算符算术运算符比较运算…

C语言中的循环队列与栈、队列之间的转换实现

引言 在数据结构的学习中,栈(Stack)和队列(Queue)是两个非常重要的概念。它们分别遵循着后进先出(LIFO)和先进先出(FIFO)的原则。在某些情况下,我们可能需要…

用友NC printBill 任意文件读取/删除漏洞复现(XVE-2024-10609)

0x01 产品简介 用友NC是一款企业级ERP软件。作为一种信息化管理工具,用友NC提供了一系列业务管理模块,包括财务会计、采购管理、销售管理、物料管理、生产计划和人力资源管理等,帮助企业实现数字化转型和高效管理。 0x02 漏洞概述 用友NC printBill 接口处存在任意文件读…

PDF编辑阅读器PDF Expert for Mac v3.10.1中文激活版

PDF Expert for Mac是一款易于使用的 PDF 编辑器和注释器,专为 Mac 设备设计。它允许用户轻松查看、编辑、签名、注释和共享 PDF。该软件使用户能够向他们的 PDF 添加文本、图像、链接和形状,突出显示和标记文本,填写表格以及签署数字文档。它…

02-结构型设计模式(共7种)

1. Adapter(适配器模式) 适配器模式是一种结构型设计模式,它允许将一个类的接口转换成客户端所期望的另一个接口。这种模式通常用于解决接口不兼容的情况,使得原本由于接口不匹配而无法工作的类可以一起工作。 在 C 中,适配器模式可以通过类适…

数学建模——线性回归模型

目录 1.线性回归模型的具体步骤和要点: 1.收集数据: 2.探索性数据分析: 3.选择模型: 4.拟合模型: 5.评估模型: 1.R平方(R-squared): 2.调整R平方(Ad…

Windows11系统配置WSL2网络使它支持LAN访问

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、WSL2安装二、使用步骤1.NAT2.镜像 三、写在最后总结 前言 WSL2的出现感觉真的是一个惊喜,又想玩Linux,又怕日用搞不了的最佳替代方…

TreeMap详解:Java 有序 Map 原理与实现

哈喽,各位小伙伴们,你们好呀,我是喵手。运营社区:C站/掘金/腾讯云;欢迎大家常来逛逛 今天我要给大家分享一些自己日常学习到的一些知识点,并以文字的形式跟大家一起交流,互相学习,一…

[初学者必看]JavaScript 简单实际案例练习,锻炼代码逻辑思维

文章目录 创意小项目合集:从简易图片轮播到购物车1. 图片轮播器2. 动态列表3. 模态框(Modal)4. 简单的表单验证5. 简易待办事项列表(Todo List)6. 简易图片画廊7. 简易时钟8. 简易搜索框高亮9. 简易颜色选择器10. 简易…

【知识碎片】2024_05_14

本篇记录了两道关于位运算的选择题,和一道有点思维的代码题。 C语言碎片知识 求函数返回值,传入 -1 ,则在64位机器上函数返回( ) int func(int x) {int count 0;while (x){count;x x&(x - 1);//与运算} return c…

java项目之实验室管理系统(springboot+vue+mysql)

风定落花生,歌声逐流水,大家好我是风歌,混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的实验室管理系统。项目源码以及部署相关请联系风歌,文末附上联系信息 。 项目简介: 实验室管理系统的主要使用…

OpenAI 推出革命性新模型 GPT-4o:全能AI的新纪元

GPT-4o 模型的推出预示着人工智能领域的又一次飞跃,它将如何改变我们的世界? 在人工智能的快速发展浪潮中,OpenAI 再次站在了技术革新的前沿。2024年5月14日,OpenAI 宣布了其最新旗舰模型 GPT-4o,这不仅是一个简单的版…

2024CCPC全国邀请赛(郑州)暨河南省赛

2024CCPC全国邀请赛(郑州站)暨河南省赛 一铜一银,虽不是线下第一次参赛但是第一次拿xcpc奖牌,还有个国赛奖真是不戳。感谢学长,感谢队友! 虽然遗憾没有冲到省赛金,不过还有icpc商丘&#xff08…

HTTP基础概念和HTTP缓存技术

什么是HTTP HTTP是超文本传输协议,主要分为三个部分:超文本、传输、协议。 超文本是指:文字、图片、视频的混合体。传输是指:点与点之间的信息通信。协议是指:通信时的行为规范或约定 HTTP常见字段 字段名 解释 例…

Android存储文件路径的区别

一、Android存储简介 Android系统分为内部存储和外部存储 从Android6.0开始不断在更新存储权限 外部存储路径的开头:storage/emulated/0 内部存储文件路径的开头:/data/user/0/应用的包名(packageName) 在设备上对应的目录为/data…

Leetcode2105. 给植物浇水 II

Every day a Leetcode 题目来源:2105. 给植物浇水 II 解法1:双指针 设 Alice 当前下标为 i,初始化为 0,水量为 a,初始化为 capacityA;Bob 当前下标为 j,初始化为 n-1,水量为 b&am…

flutter开发实战-compute将工作交由isolate处理

flutter开发实战-compute将工作交由isolate处理 最近查看flutter文档时候,看到了compute可以将工作交由isolate处理。通过 Flutter 提供的 compute() 方法将解析和转换的工作移交到一个后台 isolate 中。这个 compute() 函数可以在后台 isolate 中运行复杂的函数并…

string功能介绍(普及版)

目录 1。初始化(好几种方式),npos和string的使用说明 2。string的拷贝,隐式类型转换,[],size,iterator,begin,end,reverse,reverse_iterator&am…

回归预测 | Matlab实现DBO-ESN蜣螂算法优化回声状态网络多输入单输出回归预测

回归预测 | Matlab实现DBO-ESN蜣螂算法优化回声状态网络多输入单输出回归预测 目录 回归预测 | Matlab实现DBO-ESN蜣螂算法优化回声状态网络多输入单输出回归预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.Matlab实现DBO-ESN蜣螂算法优化回声状态网络多输入单输出…

图像融合-下游任务(目标检测、实例分割、深度估计、局部区域细节放大)

下游任务: 采用目标检测、实例分割和深度估计的下游任务来验证图像融合结果质量。 文章目录 下游任务:1.目标检测2.实例分割3.深度估计局部细节放大工具Update1.目标检测 YOLOv8:https://github.com/ultralytics/ultralytics 步骤内容第一步下载项目到本地第二步安装READ…