JVM

JMM之再看看Volatile

2019-01-23  本文已影响1人  AlanKim
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 
}

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

上一篇 下一篇

猜你喜欢

热点阅读