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

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

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

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

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

相关推荐
2301_32241428043 小时前
基于SpringBoot+Vue的休闲零食超市管理系统的设计与实现59004-计算机毕设 原创(免费领源码+部署教程+开发环境)
java·vue.js·spring boot·redis·mysql·决策树·推荐算法
懂点Tech4 小时前
ctfshow MISC入门图片篇(信息附加):misc41解题思路
开发语言·python
金銀銅鐵5 小时前
[Java] 用 GUI 展示 EnumSet 增减元素时的内部变化(上)
java·后端·ai编程
catchadmin5 小时前
PHP 8.7 协程新提案 让跨协程代码共享请求身份
开发语言·php
福建佰胜张工6 小时前
SMC 薄型气爪 MHF2-16D1 深度解析:平行开闭、低剖面结构在自动化装配中的选型与落地
java·服务器·安全·自动化
念何架构之路6 小时前
go-grpc设计思想
开发语言·后端·golang
catoop6 小时前
API Key 设计与安全生成方案:结构拆解、数据库设计与 Java 实现
java·数据库·安全
system_error_x6 小时前
SpringBoot 集成 Thymeleaf 简洁入门
java·spring boot·后端
酷炫码神6 小时前
Day 10|Pinia 状态管理:把散落在各组件的数据,收进一个全局仓库
android·开发语言·javascript
驭渊的小故事7 小时前
Spring AI Alibaba 实战:从maven依赖治理到声明式工具调用
java·人工智能·spring