【多线程-从零开始-伍】volatile关键字和内存可见性问题

volatile 关键字

import java.util.Scanner;  public class Demo2 {  private static int n = 0;  public static void main(String[] args) {  Thread t1 = new Thread(() -> {  while(n == 0){  //啥都不写  }  System.out.println("t1 线程结束循环");  }, "t1");  Thread t2 = new Thread(() -> {  Scanner scanner = new Scanner(System.in);  System.out.println("请输入一个整数:");  n = scanner.nextInt();  }, "t2");        t1.start();  t2.start();  }
}
  • 当我们输入一个非 0 的数,理应 t1 中循环条件就不成立,将会打印“线程结束循环”,但实际上输入 1 后,t1 没有任何动静
  • 我们通过 jconsole 可以看到 t1 线程仍是持续工作的image.png|353
  • 上述问题的原因,就是“内存可见性问题

内存可见性问题

层次空间速度成本数据
CPU 寄存器掉电后丢失
内存中等中等中等掉电后丢失
硬盘掉电后不丢失
while(n == 0) {}
  • 上面代码中的这个操作,循环会执行非常多次,每次循环,都要执行一个 n == 0 这样的判定
    1. 从内存读取数据到寄存器中(读取内存,相比之下,这个操作的速度非常慢)
    2. 通过类似 cmp 指令,比较寄存器和 0 的值(这个指令执行速度非常快)
  • 此时 JVM 执行这个代码的过程的时候,发现:每次执行循环操作的开销非常大,并且每次执行的结果都是一样的
  • 并且 JVM 根被没有意识到,用户可能在未来会修改 n,于是 JVM 就做了一个大胆的操作——直接把这个操作给优化掉了
    • 每次循环,不会重新读取内存中的数据,而是直接读取寄存器/cache 中的数据(缓存的结果)
  • JVM 做出上述决定之后,此时意味着循环的开销大幅度降低了,但当用户修改 n 的时候,内存中的 n 已经改变了,但是由于 t1 线程每次循环,不会真的读内存,所以感知不到 n 的改变
  • 内存中的 n 的改变,对于线程 t1 来说是“不可见的”,这样就引起了 bug
  • 内存可见性问题本质上是编译器/JVM 对代码进行优化的时候,优化出了 bug
  • 如果代码是单线程的,编译器/JVM 的代码优化一般都是非常准确的,优化之后,不会影响到逻辑
  • 但是代码如果是多线程的,编译器/JVM 的代码优化就可能出现误判(编译器/JVM 的 bug),导致不该优化的地方也给优化了,于是就造成了内存可见性问题

[!quote] 编译器问啥要做优化?

  • 有些程序员写出来的代码太低效了,为了能降低程序员的门槛,即使你的代码写的一般,最终执行也不会落下风
  • 因此一些主流的编译器,都会好引入优化机制(优化手段是多种多样的)
  • 优化就是编译器自动调整你写的代码,保持原有逻辑不变的前提下,提高代码的执行效率
  • 代码优化的效果是非常明显的

  • 若一个服务器在开启优化的时候启动时间为 10 min,那么在不开启优化的时候,启动时间可能会在 30 min+

若在 while 循环中加入一个 sleep 操作

while(n == 0) {Thread.sleep(10);
}
System.out.println("t1 线程结束循环");//在输入1后,成功输出:"t1 线程结束循环"
  • 说明加入 sleep 之后,刚才谈到的针对读取 n 内存数据的优化操作不再进行了
  • 因为和读取内存相比,sleep 的开销更大,远远超过了读取内存,就算把读取内存的操作优化掉,也没有意义,杯水车薪

volatile 关键字的用法

  • volatile 关键字修饰一个变量,提示编译器说这个变量是“易变”的
  • 编译器进行上述优化的前提,是编译器认为,针对这个变量的频繁读取,结果都是固定的
  • 使用 volatile 关键字修饰变量之后,编译器就会禁止上述的优化,确保每次循环都是从内存中重新读取数据
private static volatile int n = 0;
  • 编译器的开发者,知道这个场景中可能出现误判,于是就把权限交给程序员,让程序员能够部分的干预到优化的进行
  • 引入 volatile 的时候,编译器生成这个代码的时候,就会给这个变量的读取操作附近生成一些特殊的指令,称为“内存屏障”,后续 JVM 执行到这些特殊指令,就知道不能进行上述优化了

volatile 只是解决内存可见性问题,不能解决原子性问题,如果两个线程针对同一个变量进行修改(count++),volatile 也无能为力

[!quote] 网络上“内存可见性”问题:

  • 工作内存(其实就是 CPU 的寄存器和 cache)
  • 主内存

  • 整个 Java 程序持有这个主内存,每个 Java 程序又有一份自己的工作内存
  • 像上述例子中的内存变量 n,本身是在主内存中,在 t1 和 t2 线程工作的过程中,就会把主内存的数据拷贝到>工作内存中
  • t2 如果修改了 n,先修改工作内存,再写回到主内存中。t1 读取 n 的时候,则是从主内存加载到工作内存,接下来的判定都是依照工作内存的值来进行判定的。此时 t2 修改了主内存,对于 t1 的工作内存未产生影响,从而出现了上述内存可见性问题

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++类和对象——中

1. 类的默认成员函数 默认成员函数就是⽤⼾没有显式实现,编译器会⾃动⽣成的成员函数称为默认成员函数。⼀个类,我们不写的情况下编译器会默认⽣成以下6个默认成员函数,需要注意的是这6个中最重要的是前4个,最后两个取地址重载不…

差分专题的练习

神经&#xff0c;树状数组做多了一开始还想着用树状数组来查询差分数组&#xff0c;但是我们要进行所有元素的查询&#xff0c;直接过一遍就好啦 class Solution { public:int numberOfPoints(vector<vector<int>>& nums) {vector<int> c(105, 0);for (i…

Leetcode—233. 数字 1 的个数【困难】

2024每日刷题&#xff08;152&#xff09; Leetcode—233. 数字 1 的个数 算法思想 参考自k神 实现代码 class Solution { public:int countDigitOne(int n) {long digit 1;long high n / 10;long low 0;long cur n % 10;long ans 0;while(high ! 0 || cur ! 0) {if(cu…

多线程用不用ArrayList?

​ 博客主页: 南来_北往 系列专栏&#xff1a;Spring Boot实战 引言 多线程使用是指在单个程序中同时运行多个线程来完成不同的工作。 多线程是计算机领域的一个重要概念&#xff0c;它允许一个程序中的多个代码片段&#xff08;称为线程&#xff09;同时运行&#xff0…

Cache结构

Cache cache的一般设计 超标量处理器每周期需要从Cache中同时读取多条指令&#xff0c;同时每周期也可能有多条load/store指令会访问Cache&#xff0c;因此需要多端口的Cache L1 Cache&#xff1a;最靠近处理器&#xff0c;是流水线的一部分&#xff0c;包含两个物理存在 指…

鲜花销售小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;商家管理&#xff0c;鲜花信息管理&#xff0c;鲜花分类管理&#xff0c;管理员管理&#xff0c;系统管理 微信端账号功能包括&#xff1a;系统首页&#xff0c;购物车&#xff0…

Linux 操作系统速通

一、安装虚拟机 1. VmWare 安装下载 vmware workstation pro 16 下载 win R 输入 ncpa.cpl 确保网卡正常 2. CentOS 系统下载 CentOS 系统下载 将 CentOS 系统安装到虚拟机 3. 查看虚拟机 IP 命令 ifconfig 4. finalShell 安装下载 finalShell 下载 输入用户名一般是 ro…

Html实现全国省市区三级联动

目录 前言 1.全国省市区的Json数据 2.找到Json数据文件(在此博文绑定资源)之后&#xff0c;放到resource目录下。 3.通过类加载器加载资源文件&#xff0c;读取Json文件 3.1 创建JsonLoader类 3.2 注入JsonLoader实体&#xff0c;解析Json文件 4.构建前端Html页面 5.通过…

如何在国外市场推广中国游戏

在国外市场推广中国游戏需要一种考虑文化差异、市场偏好和有效营销渠道的战略方法。以下是成功向国际观众介绍和推广中国游戏的关键步骤和策略&#xff1a; 进行市场调研 了解目标市场&#xff1a;首先确定哪些外国市场对你的游戏最具潜力。考虑类似游戏类型的受欢迎程度、玩…

通过数组中元素或者key将数组拆分归类成新的二维数组

处理前的数组: 处理后的数组: 你希望根据 riqi 字段将这个数组拆分成多个二维数组,每个二维数组包含相同日期的项。在ThinkPHP中,你可以使用PHP的数组操作来实现这一拆分操作。以下是如何按照 riqi 字段拆分成新的二维数组的示例代码: $splitArrays = [];foreach ($list…

YOLOv6训练自己的数据集

文章目录 前言一、YOLOv6简介二、环境搭建三、构建数据集四、修改配置文件①数据集文件配置②权重下载③模型文件配置 五、模型训练和测试模型训练模型测试 总结 前言 提示&#xff1a;本文是YOLOv6训练自己数据集的记录教程&#xff0c;需要大家在本地已配置好CUDA,cuDNN等环…

opencascade TopoDS、TopoDS_Vertex、TopoDS_Edge、TopoDS_Wire、源码学习

前言 opencascade TopoDS转TopoDS_Vertex opencascade TopoDS转TopoDS_Edge opencascade TopoDS转TopoDS_Wire opencascade TopoDS转TopoDS_Face opencascade TopoDS转TopoDS_Shell opencascade TopoDS转TopoDS_Solid opencascade TopoDS转TopoDS_Compound 提供方法将 TopoDS_…

Spring快速学习

目录 IOC控制反转 引言 IOC案例 Bean的作用范围 Bean的实例化 bean生命周期 DI 依赖注入 setter注入 构造器注入 自动装配 自动装配的方式 注意事项; 集合注入 核心容器 容器的创建方式 Bean的三种获取方式 Bean和依赖注入相关总结 IOC/DI注解开发 注解开发…

抽象代数精解【8】

文章目录 希尔密码矩阵矩阵基本概念行列式基本概念特殊矩阵关于乘法运算构成群 加解密原理密钥加密函数解密函数 Z 26 上的运算&#xff08; Z 256 与此类似&#xff09; Z_{26}上的运算&#xff08;Z_{256}与此类似&#xff09; Z26​上的运算&#xff08;Z256​与此类似&…

sql注入知识整理

sql注入知识整理 一、SQL注入概念 SQL注入就是用户输入的一些语句没有被过滤&#xff0c;输入后诸如这得到了数据库的信息SQL 注入是一种攻击方式&#xff0c;在这种攻击方式中&#xff0c;在字符串中插入恶意代码&#xff0c;然后将该字符串传递到 SQL Server 数据库引擎的实…

递归.python

目录 一、认识递归 二、阶乘问题 三、经典例题&#xff1a;汉诺塔问题 一、认识递归 递归&#xff1a;即方法&#xff08;函数&#xff09;自己调用自己的一种特殊编程写法。 函数调用自己&#xff0c;即称之为递归调用。 def func(): If ....: func() return ..... 递归…

ESP8266使用舵机以及16路PWM舵机PCA 9685的使用方式

PWM全称 50Hz也就是一秒内变换50次 根据上面的公式 一个高电平一个低电平叫一个脉冲。 例如每个脉冲占20毫秒&#xff0c;那么他的频率是多少&#xff1f; 就是用1去除以他的周期&#xff0c;也就是我们上面说的20&#xff0c;那么就是除0.02,1秒等于1000毫秒&#xff0c;20…

PostgreSQL11 | 触发器

本文章代码已在pgsql11.22版本上运行且通过&#xff0c;展示页由pgAdmin8.4版本提供 上一篇总结了原著的第十章有关pgsql的视图的用法&#xff0c;本篇将总结pgsql的触发器的用法。 触发器 使用触发器可以自动化完成一些在插入数据或修改数据时&#xff0c;某些需要同期同步的…

bat批处理文件 —— 用于自动化环境配置和项目执行

文章目录 一、什么是 bat &#xff1f;1.1、支持 bat 的编辑软件1.2、常用命令 三、项目实战3.1、入门案例3.2、&#xff08;自动化&#xff09;环境配置与python库安装3.3、将 bat 当成一个简易的 .exe 可执行文件 四、标识符详解4.1、rem&#xff1a;添加注释4.2、echo off&a…

8.15 C++作业

输入一组字符&#xff0c;实现各字符的归类统计 #include <iostream> #include <string.h>using namespace std;namespace xiaoli {string str;int len; } using namespace xiaoli;int main() {getline(cin,str);//识别空格len str.size();int a0,b0,c0,d0,e0;fo…