重排序和内存屏障指令
Posted coderDu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了重排序和内存屏障指令相关的知识,希望对你有一定的参考价值。
参考及相关文献:
英语好有时间的同学,建议瞄一眼此博文去读参考文献内容。
1. 写缓冲区
现在的处理器使用写缓冲区临时保存写入内存的数据,写缓冲区的优点是:
- 可以保证指令持续运行,避免由于处理器等待向内存写入而产生的停顿延迟;
- 以批处理的方式刷新写缓冲区,及合并写缓冲区中对同一个内存地址的多次写操作,可以减少对内存总线的占用。
由于写缓冲区仅仅对自己的处理器可见,因此会导致各个处理器对内存操作的顺序可能与内存实际的操作(即受到数据修改后的值并做改变的顺序)执行顺序不一致。。示意如下:
现代处理器都是用了缓冲区,并且都允许对写-读Store-Load
操作重排序:
2. java内存模型的内存屏障指令
为了禁止处理器特定类型的重排序(通常指x86平台的Store-Load
),java编译器通过在适当的(与代码语义一直)的位置插入内存屏障指令来禁止特定类型的处理器重排序。四类内存屏障指令如下:
1. Load-Load Barrier:
示例:Load1;LoadLoad;Load2
解释:保证装载动作Load1早于Load2即以后所有的Loadn动作,但是对于屏障前后的Store操作并无影响;
2. Store-Store Barrier:
示例:Stroe1;StroeStore;Store2
解释:确保Stroe1动作将数据刷新到内存(使得数据对其他处理器可见),早于Store2及其以后所有Storen动作的执行。同理对Loadn操作无影响;
3. Load-Stroe Barrier:
示例:Load1;Load-Stroe;Store2:
解释:屏障指令之前的所有Load操作都早于屏障之后所有指令的装载动作(刷新数据到主存);
4. Store-Load barriers——全能型,
1)保证屏障之前的所有访问操作完成之后才执行屏障之后的内存访问操作——所谓访问操作包括读取和装载。
[注]:注意Store-Load
是全能型的,会屏蔽前后所有类型指令的重排。
3. volatile内存语义的实现
java内存模型为了实现volatile
可见性和禁止指令重排两个语义,使用如下内存屏障插入策略:
每个
volatile
写操作前边插入Store-Store
屏障,后边插入Store-Load(全能)
屏障;每个
volatile
读操作前边插入Load-Load
屏障和Load-Stroe
屏障;
如图所示:写volatile
屏障指令插入策略可以保证在volatile
写之前,所有写操作都已经刷新到主存对所有处理器可见了。其后全能型屏障指令为了避免写volatile
与其后volatile
读写指令重排序。
读volatile
时,会在其后插入两条指令防止volatile
读操作与其后的读写操作重排序。
4.单利模式中的内存屏障
public class Singleton {
private static volatile Singleton singleton=null;
private Singleton(){}
public static Singleton getSingleton(){
if(singleton==null)
//此处读取singleton,后边加入load-load和load-store屏障
{
synchronized (Singleton.class){
if(singleton==null)
//此处读取singleton,后边加入load-load和load-store屏障
{
//此处写singleton:加入Store-Store屏障
singleton=new Singleton();
//fixme 此处写singleton:加入Store-Load屏障,
// fixme 此处屏障防止了第二个线程对主存数据singleton读取早于此前线程创建singleton并装载进主存之前
}
}
}
return singleton;
}
}
以上是关于重排序和内存屏障指令的主要内容,如果未能解决你的问题,请参考以下文章
volatile关键字?MESI协议?指令重排?内存屏障?这都是啥玩意
Android-JMM内存模型-指令重排-Happens-Before原则-volatile-lock指令-内存屏障