多线程编程6——使用 volatile 解决问题可见性问题

一、内存可见性问题 

内存可见性问题是出现线程安全问题的原因之一

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

一个线程针对一个变量进行读取操作,另一个线程针对这个变量进行修改操作,此时读到的值不一定是修改后的值,出现了线程安全问题,这就是内存可见性问题。

2、为什么会产生内存可见性问题?

因为这个读线程没有感知到变量的变化,归根结底是jvm/编译器在多线程环境下优化时产生了误判。

3、从JMM角度表述内存可见性问题

对于内存可见性问题,其它一些资料会谈到JMM(Java Memory Model,Java内存模型)。

从JMM角度表述内存可见性问题:

Java程序里,有个主内存,每个线程还有自己的工作内存(工作内存不是内存,翻译的锅,相当于工作存储区)(线程t1和线程t2分别有各自的工作内存),t1线程进行读取的时候,只是读取了工作内存的值。t2线程进行修改的时候,先是修改了工作内存的值,然后再把工作内存的内容同步到主内存中。但是由于编译器的优化,导致t1线程没有重新从主内存同步数据到工作内存,读到的结果就是“修改之前”的结果。

主内存(main memory)就是内存,工作内存(work memory)可以理解为工作存储区,不是内存,是CPU上存储数据的单元(工作内存 = CPU的寄存器+CPU的缓存cache

为啥会有cache?

因为CPU读取寄存器的速度比CPU读取内存的速度快太多太多了,于是CPU内部就有了cache(分CPU,有的CPU内部有cache,有的没有)

寄存器、cache、内存的关系:

寄存器:存储空间小,读写速度快,贵

cache:存储空间居中,读写速度居中,成本居中

内存:存储空间大,读写速度慢,便宜(相比寄存器)

当CPU读取一个内存数据的时候,可能直接读内存,可能读cache,也可能读寄存器。(如果CPU是读cache或寄存器,说明cache或寄存器中已经有从内存中同步的数据了。)无论是从内存中读取数据,还是从cache中读取数据,读取的数据都会存放在CPU的寄存器中,用来进行各种运算操作。参与运算操作的数据都是CPU寄存器里的。

二、如何解决内存可见性问题 —— volatile

1、一段出现线程安全问题的代码

有两个线程,一个成员变量flag。线程t1用来 读取变量flag 的值,线程t2用来 修改变量flag 的值,预期结果是:输出 flag 被修改成非零了

代码和输出结果如下:

class MyCounter{
    public int flag = 0;
}
public class ThreadDemo14 {
    public static void main(String[] args) {
        MyCounter myCounter = new MyCounter();
        //线程t1 用来 读取
        Thread t1 = new Thread(()->{
            while(myCounter.flag == 0){
                //这个循环体中什么都不写
            }
            System.out.println("flag 被修改成非零了");
        });
        //线程t2 用来 修改
        Thread t2 = new Thread(()->{
            Scanner scanner = new Scanner(System.in);
            System.out.println("请输入一个非零的整数:");
            myCounter.flag = scanner.nextInt();
        });

        t1.start();
        t2.start();
    }
}

 

并没有输出 flag 被修改成非零了,和预期结果不一样,代码出bug了

2、这段代码为什么会出现线程安全问题?

和内存可见性问题有关。

myCounter.flag == 0这行代码,使用汇编来理解,就是两步操作,也就是两条指令:

1、load:把内存中flag的值,读取到 CPU的寄存器 中(可能是从内存读到寄存器,也可能是先从内存读到cache,再从cache读到寄存器)

2、cmp:把CPU寄存器里的值,和0进行比较。根据比较结果,决定下一步往哪个地方执行

由于while循环中什么都没写,所以这个循环的执行速度极快(一秒钟能执行百万次循环),而循环执行这么多次,只要 t2还没修改flag的值,load的flag的值就还是原来的0。

另一方面,load操作和cmp操作的执行速度也不一样,因为load操作要读取内存,cmp操作只要读取寄存器,CPU针对寄存器的操作能比针对内存的操作快3-4个数量级,也就是说,相比cmp来说,load执行速度太慢了。

循环的执行速度极快,说明需要非常多次load和cmp,cmp执行又很快,说明基本上都是一直在load,一直在花费时间读取内存中的flag,flag的值还不变,所以JVM认为线程一直在花费时间不断的读取一个相同的数字,于是就给优化了。不再去内存中读取了,而是直接用寄存器里面未更改的值。参与运算的寄存器里的值是指CPU去工作内存读取,存放到CPU的寄存器中的数据

在线程t2  修改flag的值之前,编译器已经自作主张给优化了(编译器说:一直在浪费时间读一个不变的数,我不让你去内存读了),所以就算后来线程t2修改了flag的值,线程t1也读不到了。这就出现了bug,出现了线程安全问题。

这其实就是内存可见性问题。

3、如何解决内存可见性问题?

给这个变量加个 volatile关键字 修饰,告诉编译器,这个变量是“易变的”,你一定要每次都去内存中重新读取变量的值,指不定啥时候就变了,你可不能偷懒给优化了。

什么时候用 volatile 关键字?

解决内存可见性问题时 

4、内存可见性问题的出现是随机的,不是100%

比如上面那段出现问题的代码,在循环中加了sleep控制了循环的速度,不加volatile,运行结果也正确了。编译器的优化,很多时候是“玄学问题”,应用程序这个角度无法感知,因此稳妥一点,我们还是把该加 volatile 的地方都给加上。

三、synchronized 和 volatile 的区别

synchronized 和 volatile 的区别:

synchronized 只能修饰方法和代码块,不能修饰变量。

volatile 只能修饰变量,且修饰的是成员变量,不能修饰局部变量,因为局部变量只能在当前方法中使用,出了方法就没了。也就是说,方法里的局部变量,只能在当前线程的这个方法中使用,不能用于多线程之间的读取或修改,天然就是线程安全的。

synchronized 和 volatile 都能保证线程安全,但适用的场景不同。synchronized 是针对两个线程修改一个变量原子性问题volatile 是针对一个线程读取一个线程修改内存可见性问题。volatile能保证内存可见性,不保证原子性。如果涉及到某个代码,既需要考虑原子性,又需要考虑内存可见性,就把synchronized 和 volatile 都加上。

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

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

相关文章

学习Android的第三天

目录 Android LinearLayout 线性布局 XML 属性 LinearLayout 几个重要的 XML 属性 LinearLayout.LayoutParams XML 属性 divider (分割线) Android RelativeLayout 相对布局 RelativeLayout 布局属性 TableLayout ( 表格布局 ) TableRow 子控件的主要属性 Android Lin…

爬虫入门到精通_基础篇4(BeautifulSoup库_解析库,基本使用,标签选择器,标准选择器,CSS选择器)

1 Beautiful说明 BeautifulSoup库是灵活又方便的网页解析库,处理高效,支持多种解析器。利用它不用编写正则表达式即可方便地实线网页信息的提取。 安装 pip3 install beautifulsoup4解析库 解析器使用方法优势劣势Python标准库BeautifulSoup(markup,…

ADB的配置和使用及刷机root

ADB的配置和使用 ADB即Android Debug Bridge,安卓调试桥,是谷歌为安卓开发者提供的开发工具之一,可以让你的电脑以指令窗口的方式控制手机。可以在安卓开发者网页中的 SDK 平台工具页面下直接下载对应系统的 adb 配置文件,大小只…

05、全文检索 -- Solr -- Solr 全文检索之图形界面的文档管理(文档的添加、删除,如何通过关键字等参数查询文档)

目录 Solr 全文检索之文档管理添加文档使用 JSON 添加文档:使用 XML 添加文档: 删除文档使用 JSON 删除文档:使用 XML 删除文档: 查询文档查询文档的详细参数fq(Filter Query):过滤sort:排序sta…

LangGPT-人人都可以写高质量的prompt

使用 LangGPT,可以在几分钟内轻松上手大模型指令编写。 网址:https://github.com/EmbraceAGI/LangGPT/tree/main 手册:⭐LangGPT 结构化提示词 模版 # Role: 角色名## Profile - Author: 西堂 - Version: 0.1 - Language: 中文 - Descripti…

RocketMQ问题篇02 | Broker存储过慢异常分析

RocketMQ问题篇01 | Broker存储过慢异常分析 1、问题描述2、磁盘IO分析(排除硬件问题)3、刷盘源码分析(排除刷盘逻辑)4、macloud的告警源代码分析(定位至pageCache有问题)5、操作系统排查(排除m…

使用apifox创建一个Mock Server Api 接口

安装 下载 Apifox - API 文档、调试、Mock、测试一体化协作平台。拥有接口文档管理、接口调试、Mock、自动化测试等功能,接口开发、测试、联调效率,提升 10 倍。最好用的接口文档管理工具,接口自动化测试工具。 创建mock api项目中使用 创建项…

vio参数文件内相机imu参数的修改

imu标定工具 https://github.com/mintar/imu_utils网络上有各种IMU校准工具和校准教程,曾经花费了巨大精力跟着各种教程去跑校准。 然而,标定使用的数据都是在静止状态下录制的,我们在使用vio或者imu-cam联合标定的时候,imu确是处…

短剧小程序开发:打造高效、便捷的娱乐体验

随着移动互联网的普及和用户需求的多样化,短剧小程序作为一种新型的应用形态,逐渐受到了广大用户的青睐。短剧小程序开发旨在为用户提供一种高效、便捷的娱乐体验,让用户在忙碌的生活中轻松享受到精彩的短剧内容。本文将探讨短剧小程序开发的…

备战蓝桥杯---搜索(BFS基础1)

如果DFS是时光回溯&#xff0c;那么BFS则是影子分身。 下面是它的定义&#xff1a; 下面直接看题&#xff1a; 十分经典&#xff0c;在这注意存的时候可以用i*mj的形式&#xff0c;可以当作模板&#xff0c;下面是AC代码&#xff1a; #include<bits/stdc.h> using name…

卡诺图:逻辑相邻与几何相邻的统一

文章目录 1.一句话记住卡诺图2.卡诺图的由来、定义和特点3.填写卡诺图&#xff08;用卡诺图表示逻辑函数&#xff09;⑴根据真值表填写卡诺图⑵根据最小项&#xff08;或最大项&#xff09;填写卡诺图⑶根据函数的与或表达式填写卡诺图 4.用卡诺图化简逻辑函数⑴化简步骤⑵画圈…

c#的反汇编对抗

文章目录 前记nim攻防基础FFI内存加载加解密、编码 后记C#类型转换表nim基础 前记 随便编写一个c#调用winapi并用vs生成dll,同时用csc生成exe using System; using System.Runtime.InteropServices; namespace coleak {class winfun{[DllImport("User32.dll")]publ…

AutoCAD .NET 层次结构介绍

AutoCAD .NET API 提供了一种面向对象的编程接口&#xff0c;通过它可以与AutoCAD进行深度集成和自定义功能开发。以下是基于.NET框架下AutoCAD对象层次结构的基本介绍&#xff1a; Autodesk.AutoCAD.ApplicationServices 命名空间 根对象&#xff0c;代表运行中的AutoCAD应用程…

模板简要介绍,C++读书笔记

2014年2月3日 内容整理自《程序设计教程&#xff1a; 用C语言编程 第三版》 陈家骏 郑滔 --------------------------------------------------------------------------------------------------------------------------------- &#xff08;一&#xff09;函数模板 1…

苹果的ipad可能会缓存vue项目的数据或者pinia数据

如果你发现开发的vue项目在ipad上出现了异常&#xff0c;比如数据出现NaN的情况&#xff0c;或者computed计算属性没生效&#xff0c;或者pinia里面的数据没生效&#xff0c;可能就是ipad浏览器safari缓存了数据导致的&#xff0c;只需要清空safari里面缓存的数据就可以了&…

(java版)排序算法----【冒泡,选择,插入,希尔,快速排序,归并排序,基数排序】超详细~~

目录 冒泡排序(BubbleSort)&#xff1a; 代码详解&#xff1a; 冒泡排序的优化&#xff1a; 选择排序(SelectSort)&#xff1a; 代码详解&#xff1a; 插入排序&#xff08;InsertSort&#xff09;&#xff1a; 代码详解&#xff1a; 希尔排序(ShellSort)&#xff1a; 法一…

深度学习图像分类相关概念简析+个人举例1(ANN相关概念与计算)

&#xff08;1&#xff09;神经网络&#xff1a;英文全称Artificial Neural Network&#xff0c;简称为ANN。 神经网络是一种模仿人脑神经元结构和功能的人工智能模型。它由多个神经元&#xff08;也称节点、单元&#xff09;组成&#xff0c;每个神经元通过计算输入和权重的线…

从零开始复现GPT2(六):生成代码的实现

源码地址&#xff1a;https://gitee.com/guojialiang2023/gpt2 GPT2 模型文本生成配置生成框架文本生成类实现文本生成代码 模型 文本生成 配置 class GenerateConfig(object):def __init__(self,seq_len: int,nucleus_prob: float,use_gpu: bool):self.seq_len seq_lenself…

【C/C++ 10】扫雷小游戏

一、题目 写一个扫雷小游戏&#xff0c;每次输入一个坐标&#xff0c;若该处是地雷&#xff0c;则游戏失败&#xff0c;若该处不是地雷&#xff0c;则显示周围地雷数量&#xff0c;若扫除全部非地雷区域&#xff0c;则扫雷成功。 二、算法 设置两张地图&#xff08;二维数组&…

手把手教你开发Python桌面应用-PyQt6图书管理系统-主界面UI设计实现

锋哥原创的PyQt6图书管理系统视频教程&#xff1a; PyQt6图书管理系统视频教程 Python桌面开发 Python入门级项目实战 (无废话版) 火爆连载更新中~_哔哩哔哩_bilibiliPyQt6图书管理系统视频教程 Python桌面开发 Python入门级项目实战 (无废话版) 火爆连载更新中~共计24条视频&…