对一个 volatile 域的写, happens-before 于任意后续对这个 volatile 域的读
一、happens-before 之 volatile 变量规则
JMM 针对编译器定制的 volatile 重排序的规则,那 JMM 是怎样禁止重排序的呢? 答案是内存屏障
二、内存屏障 (Memory Barriers / Fences)
为了实现 volatile 的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序
这句话有点抽象,试着想象内存屏障是一面高墙,如果两个变量之间有这个屏障, 那么他们就不能互换位置(重排序)了,变量有读(Load)有写(Store),操作有前有后, JMM 就将内存屏障插入策略分为 4 种:
1. 在每个 volatile 写操作的前面插入一个 StoreStore 屏障
2. 在每个 volatile 写操作的后面插入一个 StoreLoad 屏障
3. 在每个 volatile 读操作的后面插入一个 LoadLoad 屏障
4. 在每个 volatile 读操作的后面插入一个 LoadStore 屏障
三、volatile 写-读的内存语义
假定线程 A 先执行 writer 方法,随后线程 B 执行 reader 方法
public class ReorderExample {
private int x = 0;
private int y = 1;
private volatile boolean flag = false;
public void writer(){
x = 42; //1
y = 50; //2
flag = true; //3
}
public void reader(){
if (flag){ //4
System.out.println("x:" + x); //5
System.out.println("y:" + y); //6
}
}
}
volatile 读的内存语义:
当读一个 volatile 变量时, JMM 会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。
所以当线程 B 执行 reader 方法时,线程 B本地内存变量无效,从主内存中读取变量到本地内存中,也就得到了线程 A 更改后的结果,这就是 volatile 是如何保证可⻅性的
线程 A 写一个volatile变量, 实质上是线程 A 向接下来将要读这个 volatile 变量 的某个线程发出了(其对共享变量所做修改的)消息
线程 B 读一个 volatile 变量,实质上是线程 B 接收了之前某个线程发出的(在写 这个 volatile 变量之前对共享变量所做修改的)消息。
线程 A 写一个 volatile 变量, 随后线程 B 读这个 volatile 变量, 这个过程实质 上是线程 A 通过主内存向线程B 发送消息。