什么是内存屏障?硬件层⾯,内存屏障分两种:读屏障(Load Barrier)和写屏障(Store Barrier)。内存屏障有两个作⽤:
- 阻⽌屏障两侧的指令重排序;
- 强制把写缓冲区/⾼速缓存中的脏数据等写回主内存,或者让缓存中相应的数据失效。
注意这⾥的缓存主要指的是CPU缓存,如L1,L2等(这里所提到的缓存是指CPU缓存,例如L1,L2等级别的缓存。这是因为在计算机系统中,还存在其他类型的缓存,比如磁盘缓存、网络缓存等等。因此,这个主句的作用是强调内存屏障所涉及到的缓存类型,避免混淆。)
编译器在⽣成字节码时,会在指令序列中插⼊内存屏障来禁⽌特定类型的处理器重排序。编译器选择了⼀个⽐较保守的JMM内存屏障插⼊策略,这样可以保证在任何处理器平台,任何程序中都能得到正确的volatile内存语义。这个策略是:
- 在每个volatile写操作前插⼊⼀个StoreStore屏障;
- 在每个volatile写操作后插⼊⼀个StoreLoad屏障;
- 在每个volatile读操作后插⼊⼀个LoadLoad屏障;
- 在每个volatile读操作后再插⼊⼀个LoadStore屏障。
示意图⼤概是这个样⼦:
LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写⼊操作执⾏前,保证Store1的写⼊操作对其它处理器可⻅。
LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写⼊操作被刷出前,保证Load1要读取的数据被读取完毕。
StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执⾏前,保证Store1的写⼊对所有处理器可⻅。它的开销是四种屏障中最⼤的(冲刷写缓冲器,清空⽆效化队列)。在⼤多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能
再介绍⼀下volatile与普通变量的重排序规则:
- 如果第⼀个操作是volatile读,那⽆论第⼆个操作是什么,都不能重排序;
- 如果第⼆个操作是volatile写,那⽆论第⼀个操作是什么,都不能重排序;
- 如果第⼀个操作是volatile写,第⼆个操作是volatile读,那不能重排序。
如果是下列情况:第⼀个操作是普通变量读,第⼆个操作是volatile变量读,那是可以重排序的:
// 声明变量
int a = 0; // 声明普通变量
volatile boolean flag = false; // 声明volatile变量
// 以下两个变量的读操作是可以重排序的
int i = a; // 普通变量读
boolean j = flag; // volatile变量读