1.保证内存可见性(强制读取内存)
内存可见性引起的 线程安全问题
如果一个线程写,一个线程读,这个时候是否会有线程安全问题?也可能存在

我们可以看到 当我们在 线程t2 中输入其他值 改变 flag值时,但是 t1 并没有结束;
这个就称为 “内存可见性”问题;
内存可见性,高度依赖编译器的优化的具体实现;上述代码稍作改变,结果就会不同;

如果不加 sleep 我们在输入的同时,线程t1 会循环很多次,资源开销会很大;但如果加入 sleep 此时,整体的资源消耗就会减少;
本质:在上面这个 读操作线程中,编译器发现,每次循环都要读取内存,开销太大,于是就把读取内存操作优化成读取寄存器操作(将存储在内存中的 flag的值 先读取到寄存器中,资源开销大时就不会直接读取内存,而是循环读取寄存器),提高效率
java 提供了 volatile(强制读取内存;开销大,效率降低,但准确性提高) 就可以使上述的优化就被强制关闭,确保每次循环条件都会重新从内存中读取数据;

2.禁止指令重排序
指令重排序引起的线程安全
(指令重排序也是 jvm 优化的一种方式)
调整原有代码执行顺序,保证逻辑不变的前提下,提高程序的效率;
——————————————————————-
举例:

自己写的代码顺序,就像买菜顺序一样,如果没有 jvm 优化执行顺序,这样的买菜顺序需要走许多路;

优化后的 买菜顺序;(jvm 主动优化);

懒汉模式中instance = new SingletonLazy(),这行代码实际会被拆分为 3 个 CPU 指令:
分配内存空间:给新对象开辟一块内存。
初始化对象:调用构造方法,初始化对象的属性等。
赋值引用:把instance指向刚分配的内存地址。
正常执行顺序是1→2→3,但编译器 / CPU 可能会指令重排为1→3→2(先赋值引用,再初始化对象,此时这块内存还未初始化;)。
如果在 1→3→2 的执行顺序下:线程A 先执行 3 此时线程B 调用getInstance(); instance 不为空,就可能直接将还未初始化内存返回给 线程B ,instance的属性可能会出现全为 “0”的情况;就会出现错误;

volatile关键字可以禁止指令重排,强制1→2→3的执行顺序;同时保证内存可见性



