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可见。
网友评论