volatile 的可见性,禁止指令重排序,无法保证原子性的理解

发布时间:2022-07-01 发布网站:脚本宝典
脚本宝典收集整理的这篇文章主要介绍了volatile 的可见性,禁止指令重排序,无法保证原子性的理解脚本宝典觉得挺不错的,现在分享给大家,也给大家做个参考。

了解volatile的原理前,我们先来看个示例代码:

public class Visualable {
    public static    boolean inITFlag=false;

    public static void main(String[] args) throws InterruptedException {

        new Thread(()->{
            System.out.PRintln("线程1开始执行");

            while (!initFlag){
              //nothing todo
             
            }

            System.out.println("线程执行1执行完毕");
        }).start();

        TimeUnit.SECONDS.sleep(2);

        new Thread(()->{
            System.out.println("线程执行2开始执行");
            initFlag=true;
            System.out.println("线程执行2执行完毕");
        }).start();


    }
}

执行后显示:System.out.println("线程执行1执行完毕"); 这行代码没执行

volatile 的可见性,禁止指令重排序,无法保证原子性的理解

 

 说明了线程2修改initFlag的值并没有让线程1感知到;为何?

这里我们就要了解Java线程内存模型了,在硬件级别来看:

volatile 的可见性,禁止指令重排序,无法保证原子性的理解

 

 

上图所示:一开始线程1和线程2都从主内存中将initFlag的值,读回了线程自己的内存中,此时线程2将值改了,但还没及时刷回主内存中,所以线程1感觉不到值的改变,因此线程1一直死循环,只要写

回了主内存,线程1就可以感觉到数据的改变,这个是由MSei缓存一致性协议决定的,由各个cpu厂商实现,简单点说,每个线程都会监听总线,数据的变更要经过总线,然后,线程监听到变量变更后,会检查

自己的内存有没该变量,有就失效自己的变量,之后从主内存中读取,既然MSEI能保证内存的可见性,为何还会有问题,这是因为线程2没有及时将变量刷回主内存,而volatile可以保证变量的修改立即刷回主内存,因此保证可见性,因此代码修改一下:

volatile 的可见性,禁止指令重排序,无法保证原子性的理解

 

 

volatile 的可见性,禁止指令重排序,无法保证原子性的理解

 

 那为何volatile没法保证变量的原子性: 我们思考一个问题,如果一个变量 int sum=0;线程1和线程2都读取到内存中,然后都做了++操作,那么线程1如果先刷回内存中,线程2的内存变量就失效了,此时sum的值值加了1

那volatile的禁止指令重排又是啥?先看个demo

volatile 的可见性,禁止指令重排序,无法保证原子性的理解

 

 为何会出现a=1 和b=1同时出现的情况,正常来说,a要等于1 证明y=1这行代码已经执行了,因为a=y,然而y=1执行了,证明b=x要先运行,那么b应该等于0才对,因为x此时等于0

出现这个结果的原因: 线程one中,a=y和x=1的代码顺序调换了,也就是指令重排了,线程Two b=x 和 y=1的代码顺序也重排了

什么时候会重排:遵循下面2个原则:

as-if-serial 语义:简单的说就是:能否重排,最重要的是,对于单线程来说,重排前后,结果不变,对于上面例子,对于线程one来说 a=y和x=1的顺序调换,结果是一样的,线程Two也是一样道理

hapPEn-before 原则:

1. 单线程happen-before原则:在同一个线程中,书写在前面的操作happen-before后面的操作。 2. 锁的happen-before原则:同一个锁的unlock操作happen-before此锁的lock操作。 3. volatile的happen-before原则: 对一个volatile变量的写操作happen-before对此变量的任意操作 4. happen-before的传递性原则: 如果A操作 happen-before B操作,B操作happen-before C操作,那么A操作happen-before C操作。 5. 线程启动的happen-before原则:同一个线程的start方法happen-before此线程的其它方法。 6. 线程中断的happen-before原则:对线程interrupt方法的调用happen-before被中断线程的检测到中断发送的代码。 7. 线程终结的happen-before原则:线程中的所有操作都happen-before线程的终止检测。 8. 对象创建的happen-before原则:一个对象的初始化完成先于他的finalize方法调用。

这些规则来描述了什么时候可以重排

如何解决指令重排,很简单,只要在变量中加入volatile 关键字即可;

volatile 禁止重排原理:内存屏障,举个例子:

int a=1;

int b=2;

我们要系统禁止这2行代码进行重排,我们需要定义一个规范,就像产品经理说,我要实现某个功能,这是一种规范,真正实现由程序员去弄,现在假设我们的规范是,只要这两行代码中有abc三个字母就禁止重排

int a=1;

abc;

int b=2;

然后我们系统发现有abc就不会重排了,具体实现这个需求,是由jvm厂商去实现,如hotsport虚拟机:

内存屏障中的abc字母在hostport中有4种情况,读读屏障(对应的字母是 loadload),写写屏障(Storestore),读写屏障(loadstore),写读屏障(storeload),汇编码:

volatile 的可见性,禁止指令重排序,无法保证原子性的理解

 

 

volatile 的可见性,禁止指令重排序,无法保证原子性的理解

 

脚本宝典总结

以上是脚本宝典为你收集整理的volatile 的可见性,禁止指令重排序,无法保证原子性的理解全部内容,希望文章能够帮你解决volatile 的可见性,禁止指令重排序,无法保证原子性的理解所遇到的问题。

如果觉得脚本宝典网站内容还不错,欢迎将脚本宝典推荐好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。