volatile的理解——Java全栈知识(43)

1、内存可见性

一个典型的例子:永不停止的循环

package com.itheima.basic;  
​  
​  
// 可见性例子  
// -Xint  
public class ForeverLoop {  
    static boolean stop = false;  
​  
    public static void main(String[] args) {  
        new Thread(() -> {  
            try {  
                Thread.sleep(100);  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
            stop = true;  
            System.out.println("modify stop to true...");  
        }).start();  
        foo();  
    }  
​  
    static void foo() {  
        int i = 0;  
        while (!stop) {  
            i++;  
        }  
        System.out.println("stopped... c:"+ i);  
    }  
}

当执行上述代码的时候,发现for()方法中的循环是结束不了的,也就说读取不到共享变量的值结束循环。

主要是因为在 JVM 虚拟机中有一个 JIT(即时编辑器)给代码做了优化。
上述代码:

 while (!stop) {  
	 i++;  
 }

在很短的时间内,这个代码执行的次数太多了,当达到了一个阈值,JIT就会优化此代码,如下:

while (true) {
i++;
}

当把代码优化成这样子以后,及时 stop 变量改变为了 false 也依然停止不了循环

解决方案:

第一:
在程序运行的时候加入vm参数-Xint表示禁用即时编辑器,不推荐,得不偿失(其他程序还要使用)
第二:
在修饰stop变量的时候加上volatile,表示当前代码禁用了即时编辑器,问题就可以解决,代码如下:

static volatile boolean stop = false;

2、禁止指令重排序

什么是指令重排序?
用 volatile 修饰共享变量会在读、写共享变量时加入不同的屏障,阻止其他读写操作越过屏障,从而达到阻止重排序的效果

image-20230505082441116

在去获取上面的结果的时候,有可能会出现4种情况

  • 情况一:先执行 actor 2 获取结果—>0,0 (正常)
  • 情况二:先执行 actor 1 中的第一行代码,然后执行 actor 2 获取结果—>0,1 (正常)
  • 情况三:先执行 actor 1 中所有代码,然后执行 actor 2 获取结果—>1,1 (正常)
  • 情况四:先执行actor1中第二行代码,然后执行actor2获取结果—>1,0(发生了指令重排序,影响结果)
    在变量上添加 volatile,禁止指令重排序,则可以解决问题

image-20230505082835588
屏障添加的示意图
image-20230505082923729

  • 写操作加的屏障是阻止上方其它写操作越过屏障排到volatile变量写之下
  • 读操作加的屏障是阻止下方其它读操作越过屏障排到 volatile 变量读之上

注意:此时 volatile 加到 x 上就不能保证正常顺序。
主要是因为下面两个原则:

  • 写操作加的屏障是阻止上方其它写操作越过屏障排到volatile变量写之下
  • 读操作加的屏障是阻止下方其它读操作越过屏障排到volatile变量读之上

所以,现在我们就可以总结一个volatile使用的小妙招:

  • 写变量让volatile修饰的变量的在代码最后位置
  • 读变量让volatile修饰的变量的在代码最开始位置

总结:

volatile 的作用有两点:
1、保证了内存的可见性。
当我们多个线程使用同一个共享变量的时候,例如 while(flag),此时这个变量由于访问内存次数太多,jit 会对其进行优化,也就是只取一次的值(true),后续一直用,就导致了其他线程修改 flag 为 false 但是执行循环的线程 1 没有访问主内存,导致了陷入死循环,一直是 true。

解决办法就是给这个 flag 变量加上 volatile 关键字保证每次循环都去主内存拿值。

2、禁止指令重排序
指令重排:用 volatile 修饰共享变量会在读、写共享变量时加入不同的屏障,阻止其他读写操作越过屏障,从而达到阻止重排序的效果。

  • 6
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

东莞呵呵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值