大哥,原来这个就是volatile啊给我翻译翻译,什tmd

给我翻译翻译,什tmd叫volatile

在Happends-before原则中,对一个volatile的写必happend-before于随后对这个volatile的读,翻译翻译就是后面的volatile读的代码必须等前面的volatile写代码完成之后才能读。

图片.png

你以为惊喜到这就结束了?请问volatile的内存语义和读写语义,你又知道多少呢?

三张图告诉你什么是内存屏障(Memory Barriers / Fences)

你可能会在某些文章中看到这样的一段结论:

      1. 在每一个volatile写前面加一个storeStore屏障
      2. 在每一个volatile写后面加一个loadStore屏障
      3. 在每一个volatile读后面加一个loadStore
      4. 在每一个volatile读后面加一个loadLoad
      
      
      
      
复制代码

看到这一段理论你是不是懵逼了?很好,下面我就用三段代码+三张图告诉你这段鬼话说什么是什么。

第一段代码+图(在每一个volatile写前面加一个storeStore屏障)

public class VolatileBarrierExample {
   //普通变量
   private int normal;

   private int i = 10;

   private int j = 0;

   //两个volatile变量
   private volatile int v1 = 1;
   private volatile int v2 = 2;


   /**
    * 在volatile写之前的操作
    */
   public void volatileWriteBefore() {
       j = i;//普通读
       j = j + 1;//普通写
       /****试想一下如果这里不增加屏障会怎样?****/
       v1 = j + 1;//volatile写
   }

   
}
复制代码

可以看到,这段代码在volatile写之前增加了一些普通读写的操作,试想一下,如果普通读/写都到volatile写之后,数据会是什么样的结果呢?所以由此我们可以得出第一个内存屏障:在volatile写之前在StoreStore屏障,避免volatile写时候拿到的数据是正常的。

图片.png

第二段代码+图(在每一个volatile写后面加一个storeLoad屏障)

public class VolatileBarrierExample {
   //普通变量
   private int normal;

   private int i = 10;

   private int j = 0;

   //两个volatile变量
   private volatile int v1 = 1;
   private volatile int v2 = 2;


 
   /**
    * 在volatile进行写之后的操作
    */
   public void volatileWriteAfter(){
       v2=v2+1;//volatile写
/****试想一下如果这里不增加屏障会怎样?****/
       i=v2;//volatile读
       j=v2+1;//volatile写
   }

  
}
复制代码

可以看到只有在volatile写之后价格storeload才能使得后续指令不会错乱,进而保证后续的volatile读写数据正常。

图片.png

第三段代码+图(在每一个volatile读后面加一个storeLoad屏障)

public class VolatileBarrierExample {
    //普通变量
    private int normal;

    private int i = 10;

    private int j = 0;

    //两个volatile变量
    private volatile int v1 = 1;
    private volatile int v2 = 2;


    

    /**
     * 在volatile读之后的操作
     */
    public void volatileReadAfter(){
        i=v2;//volatile读
        /****试想一下如果这里不增加屏障会怎样?****/
        i=i+1;//普通写
        j=i;//普通读
        v2=i+1;//volatile写
    }
}
复制代码

有了上面的基础我想这段代码的意思大家都很明白吧?说白了就是"等我读完了你们才能用

图片.png

再聊一聊volatile先写再读的内存语义

public class ReorderExample {

    private int x = 0;
    private int y = 1;
    private /*volatile*/ boolean flag = false;


    public void writter() {
        x = 42;  //代码1
        y = 50; //代码2
        flag = true;//代码3
    }


    public void reader() {
        if (flag) {//代码4
            System.out.println("x=" + x + " y=" + y);//代码5
        }
    }


}
复制代码

如上代码,假设我们有两个线程,第一个线程先对volatile变量进行写操作,第二个线程对volatile变量进行读操作。这时候线程之间的可见性是如何实现的呢?实际上在某一个线程对volatile变量进行读操作时,JMM会将该线程对应本地变量设置会无效,让其去主存中读取。是不是听着有点懵逼?一张图带你搞定这个过程。

图片.png

如图所示,当线程1对volatile变量flag进行写操作后,会通知所有线程这变量修改了,你们用的都是没用的数据,当线程2收到该请求且需要读取该数据时,就会去主存中读取了,表面像看起来就像上面说的"JMM会将该线程对应本地变量设置会无效,让其去主存中读取"

打完收工,本文源码地址

gitee.com/fugongliude…