这篇继续探讨造成线程安全的原因。
有这样一段代码,在输入跳出循环的值后,仍然执行着循环。



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