【多线程初阶】内存可见性问题 volatile

article/2025/6/24 1:22:43

文章目录

  • 再谈线程安全问题
  • 内存可见性问题
    • 可见性问题案例
    • 编译器优化
  • volatile
  • Java内存模型(JMM)

再谈线程安全问题

如果多线程环境下代码运行的结果是符合我们预期的,即在单线程环境应该有的结果,则说这个程序是线程安全的,反之,多线程环境中,并发执行后,产生bug就是线程不安全

在这里插入图片描述
上次谈到线程安全问题,我们介绍了前三种问题是如何解决的

  • 1.[根本问题] 操作系统对于线程的调度是随机的,抢占式执行
    这个原因我们无法解决,这是操作系统的底层设定,我们左右不了

  • 2.修改共享数据:多个线程同时对同一变量进行修改

这个原因更多的是和代码结构相关,可以调整代码结构,规避一些线程不安全的代码,但是这样的方案通用性还是不够,有些需求就是需要多线程对共享数据进行修改

  • 3.修改操作,不是原子的
    Java中解决线程安全问题最主要方案就是加锁

我们通过synchronized 关键字加锁操作来实现,锁属于系统提供的一个专门的机制,它能够产生互斥效果,通过互斥效果,把本来无序的并发执行编程一个局部上的串行执行,从而进一步解决线程安全问题

本篇文章我们解决第四种原因内存可见性问题

内存可见性问题

可见性指,一个线程对共享变量值进行修改,能够及时被其他线程看到最新值

可见性问题案例

问题代码示例:

public class Demo21 {private static int flag = 0;public static void main(String[] args) {Thread t1 = new Thread(() ->{while(flag == 0){}System.out.println("t1 线程结束");});Thread t2 = new Thread(() ->{Scanner scanner =new Scanner(System.in);System.out.println("请输入 flag 的值:");flag = scanner.nextInt();});t1.start();t2.start();}
}

在这里插入图片描述
在这里插入图片描述
很明显,这也是个bug ,涉及到线程安全问题,一个线程读取,一个线程修改,修改线程修改的值,并没有被读线程读到,这就是"内存可见性问题"

编译器优化

背景:
为什么要有编译器优化这样的机制呢?–>由于程序员的水平参差不齐,研究JDK的大佬们,希望通过让编译器 & JVM对程序员写的代码,自动进行优化
优点:
对于程序员原有的代码,编译器/JVM会在原有逻辑不变的前提下,对代码进行调整,使程序效率更高
缺点:

  • 编译器在编译代码时,并没有执行代码,只是根据编译的静态代码,来分析这个程序应该如何调整,才能更加高效,所以"保证原有逻辑不变",这个"保证"并非100%生效
  • 尤其在多线程中,因为并发执行随机调度的特点,执行多线程程序过程中,编译器的判断可能会出现失误,可能导致编译器的优化前后的逻辑出现细节上的偏差,

我们上述案例最后的效果并非我们预期的效果就是因为编译器优化导致的逻辑上细节有所偏差,我们接下来详细介绍是如何产生偏差的

在这里插入图片描述
上述代码中,t1 线程度循环条件 flag == 0,对其操作进行细化,会细分出两个指令,分别是读取指令(load读取flag)和比较指令(cmp)
程序会先执行读取指令load,把flag这个变量在内存中的值读取到寄存器中,才会执行比较指令cmp
就是因为load和cmp两步指令都在循环中完成且while中没有休眠限制,会在极短时间内循环多次
其中load(读内存操作)是cmp(纯CPU寄存器操作)时间开销的几千倍,因为虽然读取内存数据比读取硬盘数据要快多了,但是如果拿CPU寄存器和内存比,还是寄存器快得多

CPU寄存器模块,也可以存储一些数据
存储空间: 内存 >> 寄存器
存储速度: 内存 << 寄存器
CPU使用寄存器,是为了辅助计算,保存一些空间结果

因此执行过程中,JVM感受到 load 反复执行的结果好像都一样
JVM嘀咕,我执行那么多次读取flag的操作,发现值始终都是 0 ,既然结果都一样,既然还要反复执行那么多次,何必呢,于是编译器优化,把读取内存的操作,优化成 读取寄存器 这样的操作(把内存的值读取到寄存器了,后序在load不再重新读内存,而是直接从寄存器里取出来)
在这里插入图片描述

于是等了很多秒之后,用户真正输入新的值,真正修改 flag,此时 t1 线程已经感知不到用户的修改了 (编译器优化,使得 t1线程的读操作,不是真正的读内存)

如果稍微调整一下上述代码
在这里插入图片描述

假设本身读取 flag 的时间是 1ns 的话,如果把读内存操作优化成读寄存器 ,1ns =>0.xx ns,能把效率优化个50%以上,但是引入了 sleep之后,sleep 直接占用 1ns,此时优不优化 读内存操作,就无足轻重了

volatile

针对内存可见性问题,也不能单单指望通过sleep来解决,毕竟使用sleep会大大影响到程序的效率,我们希望不使用sleep也可以解决上述内存可见性问题

JDK大佬们知道上述内存可见性问题,在编译器优化的角度难以进行调整,就在语法中引入了 volatile 关键字,通过这个关键字来修饰某个变量,此时编译器对这个变量的读取操作,就不会被优化成读寄存器了

volatile 修饰的变量,能够保证"内存可见性"

在这里插入图片描述

前面我们讨论内存可见性时,编译器优化将读内存操作优化成直接访问工作内存(实际就是CPU的寄存器 或者 CPU的缓存),速度非常快,但是可能出现数据不一致的情况,加上volatile,强制读写内存,速度是慢了,但是数据变得更准确了
在这里插入图片描述
代码写入volatile修饰的变量

  • 改变线程工作内存中volatile变量副本的值
  • 将改变后的副本的值从工作内存中刷新到主内训

代码读取volatile修饰的变量

  • 从主内存中读取volatile变量的最新值到线程的工作内存中
  • 从工作内存中读取volatile变量的副本

注意:volatile解决的是内存可见性问题,不是解决原子性问题
volatile 和 synchronized 是有着本质区别的,synchronized能够保证原子性,volatile保证的是内存可见性

在这里插入图片描述

Java内存模型(JMM)

提到 volatile 就一定会谈到 JMM(Java Memory Model) Java内存模型

Java内存模型(JMM):Java虚拟机规范中定义了Java内存模型

目的是屏蔽掉各种硬件和操作系统内存访问差异,以实现让Java程序在各种平台下都能达到一致的并发效果

Java官方文档的术语
每个线程,有一个自己的"工作内存",同时这些线程共享同一个"主内存"
当一个线程循环进行上述读取变量操作的时候,就会把主内存中的数据,拷贝到该线程的工作内存中,后续另一个线程修改,也是先修改自己的工作内存,拷贝到主内存里
由于第一个线程仍然在读自己的工作内存,因此感知不到主内存的变化

注意:这里的术语,工作内存指的是"CPU的寄存器"

在这里插入图片描述

  • 线程之间的共享变量存储在 主内存(Main Memory)
  • 每一个线程都有自己的"工作内存"(Working Memory)
  • 当线程要读取一个共享变量的时候,会先把变量从主内存拷贝到工作内存,再从工作内存读取数据
  • 当线程要修改一个共享变量的时候,也会先修改工作内存中的副本,再同步回主内存

由于每个线程有自己的工作内存,这些工作内存中的内容相当于同一个共享变量的"副本",此时修改线程1 的工作内存中的值,线程2 的工作内存不一定会及时变化

1)初始情况下,两个线程的工作内存内容一致

在这里插入图片描述

2)一旦线程1 修改了 a 的值,此时主内存不一定能及时同步,对应的线程2 的工作内存的 a 的值也不一定能及时同步

在这里插入图片描述
此时就有了两个问题:

  • 为什么要整那么多内存?
  • 为什么要这么麻烦的拷贝来拷贝去?

1)为什么要整那么多内存?
实际上并没有这么多"内存",这只是Java规范的一个术语,属于"抽象"的 叫法,所谓的"主内存"才是真正的硬件角度的"内存",而所谓的"工作内存",则是CPU的寄存器和高速缓存

2)为什么要这么麻烦的拷贝来拷贝去?
因为CPU访问自身寄存器的速度以及高速缓存的速度,远远超过访问内存的速度(快了3-4个数量级也就是几千倍,上万倍)

比如某个代码中要连续10次读取变量的值,如果10次都从内存中读,速度是很慢的,但是如果只是第一次从内存读,读到的结果缓存到CPU的某个寄存器中,那么后9次读数据就不必直接访问内存了,效率就大大提高了

那么接下来的问题又来了,既然访问寄存器速度这么快,还要内存干嘛?
一个字:贵
在这里插入图片描述

值的一提的是,快和慢都是相对的,CPU访问寄存器速度远远快于内存,但是内存的访问速度又远远快于硬盘.对应的 CPU价格最贵,内存次之,硬盘最便宜


http://www.hkcw.cn/article/XkOxOyraoP.shtml

相关文章

YOLO机械臂丨使用unity搭建仿真环境,YOLO算法识别,Moveit2控制

文章目录 前言搭建开发环境在window中安装Unity创建Docker容器&#xff0c;并安装相关软件运行测试改进添加删除节点前的函数调用 报错❌框选节点的时候报错❌如果无法控制机械臂&#xff0c;查看rviz2的终端&#xff0c;应该会有❌规划路径超出范围 参考 前言 本项目介绍通过…

论文略读:Auto-Regressive Moving Diffusion Models for Time Series Forecasting

AAAI 2025 在这篇论文中&#xff0c;时间序列的演进&#xff08;)被概念化为一个扩散过程 时间序列的每一步都可以看成是扩散模型的一个状态未来序列&#xff08;下标表示在序列中的位置&#xff0c;上标表示在扩散模型中的状态&#xff09;作为前向扩散&#xff08;演进&…

外卖小哥误入工地1个多小时没能出来 最终只能报警求助

近日,一名外卖小哥在送外卖时,为节约时间,跟着导航抄小路,没想到自己看错了路线,被困在了一个在建工地内1个多小时,最终只能报警求助。责任编辑:zx0002

贾冰瘦了 少吃多动见成效

5月31日,演员贾冰的妻子发布了一段视频,祝福大家端午节快乐,并配文“从此我家多了个瘦子”。两人合影中,贾冰明显瘦了很多。评论区里,很多人询问他如何瘦下来,甚至有人表示他瘦得认不出来了。贾冰妻子回复说,主要是通过少吃和运动来达到减肥效果,有时候一天只吃一顿饭。…

犯罪分子“轻易”作案 竟是这种警示牌在指路

机房和弱电井房都是整栋建筑或整个企业网络通信的重要组成部分,如果机房是网络通信设备的“核心大脑”和“运行中心”,那么弱电井房就是连接网络通信设备的“垂直通道”和“中转站”,就好比“心脏”和“血管”相辅相成。但往往有些企业却忽略了“血管”的重要性,仅仅挂上“…

用servlet写的博客系统

数据库的设计&#xff1a; 设计好对应的表结构&#xff0c;把数据库相关的代码封装起来。 a)找到实体&#xff1a; 博客&#xff08;blog表&#xff09; 用户&#xff08;user表&#xff09; b)确认实体之间的关系 一对多 一个博客属于一个用户&#xff0c;一个用户可以…

街坊打麻将赌博被拘留 ​广州一直在重拳出击打麻将赌博行为

广州一直在重拳出击打麻将赌博行为。一黄埔街坊说自己因打麻将而被依法拘留5天罚款500元,下面评论并不多,仍看见多位广州ip地址的条友声称自已有同样或类似遭遇。有打麻将关十天的,有关两天罚五百的,有关半个月罚一千的。虽然无法证实或证伪,但也可以供参考一二。我认识的…

领域驱动设计(Domain-Driven Design, DDD)

博主介绍&#xff1a;✌全网粉丝5W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面有丰富的经验…

江科大RTC实时时钟hal库实现

首先&#xff0c;因为的LSE无法启振&#xff0c;所以我使用LSI当作RTCCLK,LSI无法由备用电源供电&#xff0c;故主电源掉电时&#xff0c;RTC走时会暂停。 hal库相关函数 时分秒 typedef struct {uint8_t Hours; /*!< Specifies the RTC Time Hour.This param…

从“人防”到“智防”,智驱力助力危化品企业智能化转型

化工和危险化学品企业一直是国家安全生产监管的重点领域之一。近年来&#xff0c;随着AI技术的快速发展&#xff0c;越来越多的传统工业场景开始引入人工智能技术&#xff0c;实现从“人防”向“技防”的转变。多地应急管理厅也相继出台相关政策&#xff0c;推动视频智能分析系…

Nacos 2.4.3 登录配置

1&#xff0c; 调整配置 完整配置文件 # # Copyright 1999-2021 Alibaba Group Holding Ltd. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy…

Cursor 0.51 全网首歌新功能深度体验:Generate Memories 让 AI 编程助手拥有“记忆“

写在前面 你是否遇到过这样的困扰:每次开启新的 Cursor 对话,都要重新向 AI 解释项目背景、技术栈、架构设计?或者当对话内容过多时,AI 就"忘记"了之前讨论的重要信息? 如果你有过这样的经历,那么 Cursor 0.51 版本新推出的 Generate Memories 功能绝对是你的…

如何在WHM中配置远程数据库访问

在远程数据库访问方面&#xff0c;cPanel和WHM之间存在一些差异&#xff1a; 在WHM中添加的主机将应用于所有cPanel用户帐户及其关联的MySQL用户。 cPanel用户无法永久删除由系统管理员root账户在WHM中添加的主机。 要允许远程主机访问MySQL数据库&#xff0c;请导航到侧边栏…

强大的PDF编辑工具,操作方便 ,长久使用

这是一款能够让每一个用户都能在这里轻松实现最简单的编辑方式&#xff0c;一站式完成PDF文件处理&#xff0c;较于前面几个版本&#xff0c;这个版本整体界面比较清爽&#xff0c;用户可以在这里一站式完成PDF编辑&#xff0c;在这里快速修改&#xff0c;编辑、创建、电子签名…

上海迪士尼游客打架官方通报 拍照冲突致肢体冲突

昨天21:45,浦东公安分局发布微博称,5月31日18时许接到报警,迪士尼乐园内发生打架事件。经初步调查,闫某某(男,22岁)与女友在拍照时,因刘某某(男,36岁)夫妻的女儿进入拍摄画面,双方发生口角后引发肢体冲突,造成闫某某和刘某某互有皮外伤,小女孩未受伤。目前,调查…

蔚来5月交付23231辆 同比增长13.1%

6月1日,蔚来公司公布了5月份的交付数据。当月共交付新车23231台,同比增长13.1%。具体来看,蔚来品牌交付了13270台;乐道品牌交付了6281台;firefly萤火虫品牌交付了3680台。截至目前,蔚来公司累计交付新车总数达到760789台。其中,蔚来品牌累计交付710655台;乐道品牌累计交…

沈白高铁进入开通运营倒计时 联调联试启动

6月1日,国家“十四五”规划重点高速铁路建设项目——沈阳至长白山高速铁路正式启动联调联试,标志着沈白高铁进入开通运营的倒计时。沈白高铁起自沈阳北站,途经辽宁省沈阳市、沈抚新区、抚顺市,以及吉林省通化市、白山市、延边州、长白山管委会,最终引入敦化至白河铁路长白…

台艺人刘乐妍称自己是中国人:反对"台独"是世界潮流,也是台湾唯一出路

六一发文称自己是中国人!台湾艺人刘乐妍:反对"台独"是世界潮流,也是台湾唯一出路!责任编辑:zx0002

Cesium使用primitive添加点线面(贴地)

// 创建一个图元集合const primitives viewer.scene.primitives.add(new Cesium.PrimitiveCollection());1、点上图 // 定义点的位置&#xff08;中国不同城市的经纬度&#xff09;const points [{ lon: 116.4074, lat: 39.9042, name: "北京" },{ lon: 121.4737, …

杨明洋社媒晒国足训练照 备战印尼全力以赴

国脚杨明洋更新了社交媒体,分享了自己随国足备战与印尼比赛的照片。国足目前在上海进行最后两场18强赛的备战工作,并计划于今日启程前往雅加达。杨明洋表示,首次入选国家队感到非常荣幸,能够成为其中一员对他来说意义重大。责任编辑:zx0176