Cpu为了提高效率会对指令进行重排序,以适合cpu的顺序运行。但是指令重排会遵守As-if-serial的规则,就是所有的动作Action)都可以为了优化而被重排序,但是必须保证它们重排序后的结果和程序代码本身的应有结果是一致的。所以这种情况在单线程中不会出现什么问题。而对于多线程,这个规则就失效了,所以可能会导致结果出现问题。
解决办法就是内存屏障,也叫内存栅栏。是一种屏障指令,cpu指令。Java中的实现方式就是使用volatile关键字,既可以解决可见性,又可以禁止指令重排。
硬件层的内存屏障有Load Barrier
和 Store Barrier
即读屏障和写屏障。而java层的屏障有四种,分别是上面两种的组合,分别是:
LoadLoad屏障:
抽象场景:Load1; LoadLoad; Load2
Load1 和 Load2 代表两条读取指令。在Load2要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
StoreStore屏障:
抽象场景:Store1; StoreStore; Store2
Store1 和 Store2代表两条写入指令。在Store2写入执行前,保证Store1的写入操作对其它处理器可见
LoadStore屏障:
抽象场景:Load1; LoadStore; Store2
在Store2被写入前,保证Load1要读取的数据被读取完毕。
StoreLoad屏障:
抽象场景:Store1; StoreLoad; Load2
在Load2读取操作执行前,保证Store1的写入对所有处理器可见。StoreLoad屏障的开销是四种屏障中最大的。
volatile做了什么?
在一个变量被volatile修饰后,JVM会为我们做两件事:
1.在每个volatile写操作前插入StoreStore屏障,在写操作后插入StoreLoad屏障。
2.在每个volatile读操作前插入LoadLoad屏障,在读操作后插入LoadStore屏障。