美文网首页JVM
JMM之再看看Volatile

JMM之再看看Volatile

作者: AlanKim | 来源:发表于2019-01-23 10:12 被阅读1次
    Volatile

    具体的可见内存可见性一文中volatile相关的知识。这里只扩充一点,关于volatile的内存屏障。

    volatile写

    对于volatile变量的写,按照JMM的标准,需要插入两条内存屏障:

    StoreStore;

    volatile_write_code;

    StoreLoad;

    解释下,在写volatile之前插入了storestore指令,意味着volatile之前的普通写操作需要先于volatile操作执行,也就是不允许之前的普通写操作,与volatile写操作重排序。符合JMM对volatile关键字的规则。

    在写volatile之后,需要加入StoreLoad指令,也就是先把volatile变量的最新值刷新到主内存(store),然后再执行后续的操作。同样不允许volatile写操作与后续的读、写操作重排序。

    volatile读

    对于volatile的读,按照JMM的标准,同样需要查询两条内存屏障,但是不同于上述的写操作,这块都是插入在volatile_code之后的。

    Volatile_read_code;

    LoadLoad;

    LoadStore;

    这里的LoadLoad用于禁止下面的普通读操作与volatile读重排序,LoadStore则禁止普通写操作与volatile读操作重排序。

    省略不必要的内存屏障

    JMM默认的内存屏障插入策略非常保守,而在实际执行时,可以根据实际情况省略掉不必要的屏障。

    例如以下代码:

    class VolatileBarrierExample{
      int a;
      
      volatile int v1 = 1;
      
      volatile int v2 = 2;
      
      void readAndWrite(){
        int i = v1;  // 第一个volatile读  code_block_1;
        int j = v2;  // 第二个volatile读  code_block_2;
        a = i + j;   // 普通写            code_block_3;
        v1 = i + 1;  // 第一个volatile写  code_block_4;
        v2 = j * 2;  // 第二个volatile写  code_block_5;
      }
      ...
    }
    

    针对readAndWrite方法,理论上编译器在生成字节码的时候会加入如下内存屏障:

    code_block_1;   --- a
    
    LoadLoad;  --- b
    
    // LoadStore;  ---- c
    
    
    
    code_block_2;  --- d
    
    // LoadLoad;  ---- e
     
    LoadStore; --- f
    
    
    
    code_block_3; --- g
    
    
    
    StoreStore;  --- h
    
    code_block_4; ---i
    
    // StoreLoad; --- j
    
    
    
    StoreStore;  ---k
    
    code_block_5; ---l
    
    StoreLoad;  --- m
    

    但是实际上,有些内存屏障是可以省略的,下面详细说下

    c — 可以省掉,因为下面的普通读操作g,不可能跳过d语句的执行,d也是一条volatile

    e - 可以省掉,因为下面根本没有普通读,g是普通写

    j— 可以省略,因为下面紧跟着一条volatile写

    所以最终,c e j三行内存屏障被省略了

    JSR-133增强了Volatile的内存语义

    使得volatile变量写-读 与锁的-释放-加锁具有相同的内存语义,volatile变量与普通变量的处理都做了禁止重排序的设定。

    锁的happens-before说明

    锁的释放-获取建立的happens-before关系

    锁是java并发编程中最重要的同步机制,锁会让临界区互斥,同时释放锁的线程向获取同一个锁的线程发送通知消息。

    举个例子:

    Class MonitorExample{
      int a = 0;
      
      public synchronized void writer(){   //1
        a ++;  // 2
      } // 3
       
      public synchronized void reader(){ // 4
        int i = a;  // 5
        ......
      }  //6 
    }
    
    • 按照程序顺序原则:

      ​ 1 happens-before 2

      ​ 2 happens-before 3

      ​ 4 happens-before 5

      ​ 5 happens-before 6

    • 按照监视器原则

      ​ 3 happens-before 4 因为3释放锁,4获取同一个锁

    • 按照传递性原则

      ​ 2 happens-before 5,也就是对a的写和读有happens-before关系

    假设有两个线程A和B,A先调用writer,然后B调用reader,那么在A执行完之后,在B获取同一个锁,A在释放锁之前所有可见的共享变量,将立即变得对B可见。

    相关文章

      网友评论

        本文标题:JMM之再看看Volatile

        本文链接:https://www.haomeiwen.com/subject/qjtljqtx.html