volatile内存语义的实现

之前在我的《java内存模型》中已经提到过,重排序分为两种,编译重排序和处理器重排序。

而volatile内存语义的实现,是JMM通过限制这两种类型的某种重排序来实现的.

                                                                       volatile的重排序规则表

是否能重排序 第二个操作    
第一个操作 普通读/写 volatile读 volatile写
普通读/写     NO
volatile读 NO NO NO
volatile写     NO

按照规则表总结如下:

  • 如果第二个操作是volatile写,那么不管第一个操作是什么,都不能进行重排序。理解:因为JMM要赋予给volatile写的内存语义是将该线程的本地内存个刷新到主内存去,所以要保证在volatile写之前所修改的共享变量值全部能够刷新到共享内存中去,因此不能和之前的操作进行重排序。比如 有两个变量。int a =1;volatile boolean flag=false;进行两个操作:a=0;flag=true;如果允许进行重排序的话,执行顺序可能为flag=true;a=0;此时共享变量a的值将无法刷新到主内存中去,就无法满足volatile写的语义了。
  • 如果第一个操作是volatile读,则不允许和后面任何操作进行重排序。理解:因为JMM要赋予个volatile读的内存语义是能够接收上一个线程(写过同一个volatile变量)所发出的修改过共享变量的消息。它要保证volatile读之后的操作所使用的共享变量都是最新的。如果允许volatile变量读和之后的操作进行重排序的话,将不能保证之后的操作所使用到的共享变量是从主内存中所获取到的更新后的变量。比如有一段代码:
public class VolatileExample {

	private volatile boolean flag=false;
	private int a=0;
	
	public void write(){
		a=1;  //1
		flag=true;  //2
	}
	
	public void read(){
		if(flag){  //3
			int y=a;//4
			System.out.println(y);
		}
	}}

    在这里,A线程先调用write()方法,随后B进程调用read()方法,按照volatile读和写的内存语义,4操作y取得的值应该是更新后a的值1,但是如果允许volatile读和之后的操作进行重排序,即允许3和4进行重排序,那么y得到的值就不一定是A线程对a更新后的新值,而是本地内存中的a的值0,因为此时还没有执行volatile读操作,本地内存的值还有效。

  • 第一个操作是volatile写,第二个操作是volatile读是,不能进行重排序。理解:第一个操作是volatile写,要保证之前修改的所有共享变量刷新到主内存中去,第二个操作是volatile读,该线程的本地内存置为无效状态,因此要去主内存中去读取共享变量,此时读取到的共享变量值为volatile写之前对共享变量修改后的新值。因此,这两个顺序肯定不能换,更换的话volatile写更新后的值并没有被volatile读后面的操作获取到。

而禁止这些重排序,是通过编译器在生成字节码时,在指令序列中插入内存屏障来实现的。

内存屏障类型表

屏障类型   指令示例 说明
LoadLoad Barriers Load1;LoadLoad;Load2 确保读1先于读2
StoreStore Barriers Store1;StoreStore;Store2 确保存1先于存2
LoadStore Barriers Load1;LoadStore;Store2

确保读1先于存2

StoreLoad Barriers Store1;StoreLoad;Load2 确保存1先于读2

JMM保守的内存屏障插入策略:

    volatile写之前插入StoreStore屏障:防止volatile写之前对共享变量的写先于volatile写。

    volatile写之后插入StoreLoad屏障:防止volatile写与下面可能有的volatile读/写重排序。

    volatile读之后插入一个LoadLoad屏障:防止volatile读与下面可能有的普通读操作重排序。

    volatile读之后插入一个LoadStore屏障:防止volatile读与下面可能有的普通写操作重排序。

而不同的处理器有不同的“松紧度”内存模型,在x86处理器中,仅会对写-读进行重排序,而不会对读-读、写-写、读-写进行重排序,因此,JMM只需要在volatile写后面增加Store-Load内存屏障即实现volatile的内存语义。

猜你喜欢

转载自blog.csdn.net/fanxin_i/article/details/79893261
今日推荐