多线程-(五)线程安全之内存可见性

这篇继续探讨造成线程安全的原因。

有这样一段代码,在输入跳出循环的值后,仍然执行着循环。

与咱们预期并不符合,显然是一个 bug,是一个线程安全问题。

一个线程读取,另一个线程修改,t1 修改的值并没有被 t 读取到,我们称为"内存可见性问题"。

要解决这个问题,得把编译器拿出来说说,咱们写的每一行代码都会被编译成 .java 文件,再变成

.class 文件,最后才到 jvm 运行。编译器虽然声称自己的优化操作能够保证逻辑不变,但在多线程程序中,编译器的判断可能会出现失误。这可能导致优化后的逻辑与优化前的逻辑在细节上产生偏差,所以主要问题出在循环上。

原因是极短时间内 CPU 在判断条件上执行了大量重复读操作,flag 值又保持不变,从而导致编译器判断既然都是一样的结果,何必反复执行这么多次,于是就把读内存的操作改成读寄存器的操作,放弃了内存自然读不到修改后的值。

等到不知多久用户真正输入值修改 flag 时,t 线程就无法感知了。

所以需要对代码进行微调,加上 sleep,没错又是它。

此时再运行修改 flag 即可成功结束。

通过强制睡眠让 while 循环速度大幅下降,读取操作自然就不需要优化了。好比说,你等出租车时发现鞋带松了,你就需要以很快的速度系好,不然会影响到后边的车。但如果你等的是火车,系鞋带的速度自然就无所谓了,快慢都不会影响上车时间。

不过 sleep 虽好也不能只靠它解决内存可见性问题,因为实在太影响程序运行效率了,即使配合 interrupt 也提高不了多少。

所以大佬们为了降低难度,在语法中引入了 volatile 关键字,通过它修饰的变量,编译器对这个变量的读取操作就不会被优化成读寄存器。t1 修改值,t 就能及时看到了。

volatile 主要作用于解决内存可见性,同时会禁止指令重排序(后边应该会说到),但不能解决原子性。

既然说到 volatile,顺带简单探讨 JMM ( java内存模型 ),下面是官方文档的属于

工作内存指的是 CPU 寄存器,主内存才是我们真正说的内存。不过为什么不明确说"寄存器",而是用工作内存术语表示,原因在为了更好兼容不同硬件设备,不同 CPU 的缓存区域不一定相同。

比如作者电脑的"工作内存"

至于为什么称为缓存,因为寄存器虽然处理速度飞快,但是容量太小了,开发CPU的大佬就在CPU上搞出了另一些存储空间,后来被我们称为"缓存"。

最早只有CPU的寄存器自己玩,不过后来的数据越来越多,寄存器逐渐"装不下",就有了1级缓存,以此类推有了2,3级缓存。

------------------------------------------------------------完------------------------------------------------------------

相关推荐
CHHH_HHH4 分钟前
【Linux系统篇】深入解析Linux文件系统:从磁盘寻址到软硬链接
linux·服务器·开发语言·后端·ubuntu
yngsqq12 分钟前
窗体快速取消
java·开发语言
岁月如歌778618 分钟前
一次讲透 Redis 缓存击穿、穿透、雪崩,从原理到实战解决方案
java·后端·架构
XiaQ091941 分钟前
Linux 常用命令学习笔记(用户管理篇):用户、组与权限的钥匙
linux·开发语言·笔记·学习
不可求~1 小时前
C++ 报错交给 AI 之前,先准备好这 8 类信息
开发语言·c++·人工智能
名字还没想好☜1 小时前
用 Python struct 解析二进制协议:pack/unpack、字节序与对齐踩坑
开发语言·python·二进制·struct
AI多Agent协作实战派1 小时前
AI多Agent协作系统实战(四十六):改了十次规则,AI员工还是老样子——会话缓存的坑
java·spring·缓存
一知半解仙1 小时前
当机器人学会泛化,而我在写Java:一名后端开发者的2026年8月21日观察手记
java·开发语言·机器人
青 春 记 忆2 小时前
LeetCode 155. 最小栈|Python 解法详解
开发语言·python·leetcode
葡萄成熟时 !10 小时前
JAVA 常用API学习笔记
java·笔记·学习