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

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

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

与咱们预期并不符合,显然是一个 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级缓存。

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

相关推荐
小bo波1 小时前
从"任意文件复制"深挖Java I/O:字符流与字节流的本质抉择
java·nio·io流·后端开发·文件复制
nanxun8861 天前
记一次诡异的 Docker 容器"串包"故障排查
java
用户1563068103511 天前
Day01 | Java 基础(Java SE)
java
行者全栈架构师1 天前
Maven dependency:tree 的 8 个高级用法
java·后端
行者全栈架构师1 天前
IDEA 中 Maven 项目的 15 个红色报错快速解决方法
java·后端
令人头秃的代码0_01 天前
mac(m5)平台编译openjdk
java
唐青枫2 天前
Java JDBC 实战指南:从 Connection 到事务和连接池
java
一个做软件开发的牛马2 天前
MyBatis-Plus 从零实战:完整搭建可运行 Demo,BaseMapper 零 SQL、Wrapper 条件构造、分页插件与代码生成器详解
java·后端
用户3721574261352 天前
Java 处理 PDF 图片:提取 PDF 中的图片,并压缩 PDF 图片体积
java
用户3721574261352 天前
Java 打印 Word 文档:从基础打印到高级设置
java