Java|面试官问我什么是JMM( 四 )


为了使指令更加符合CPU的执行特性 , 最大限度的发挥机器的性能 , 提高程序的执行效率 , 只要程序的最终结果与它顺序化情况的结果相等 , 那么指令的执行顺序可以与代码逻辑顺序不一致 , 这个过程就叫做指令的重排序 。
重排序的种类分为三种 , 分别是:编译器重排序 , 指令级并行的重排序 , 内存系统重排序 。 整个过程如下所示:



指令重排序在单线程是没有问题的 , 不会影响执行结果 , 而且还提高了性能 。 但是在多线程的环境下就不能保证一定不会影响执行结果了 。
所以在多线程环境下 , 就需要禁止指令重排序 。
volatile关键字禁止指令重排序有两层意思:
  • 当程序执行到volatile变量的读操作或者写操作时 , 在其前面的操作的更改肯定全部已经进行 , 且结果已经对后面的操作可见 , 在其后面的操作肯定还没有进行 。
  • 在进行指令优化时 , 不能将在对volatile变量访问的语句放在其后面执行 , 也不能把volatile变量后面的语句放到其前面执行 。
下面举个例子:
private static int a;//非volatile修饰变量private static int b;//非volatile修饰变量private static volatile int k;//volatile修饰变量private void hello() {
   a = 1;  //语句1
   b = 2;  //语句2
   k = 3;  //语句3
   a = 4;  //语句4
   b = 5;  //语句5
   //以下省略...

变量a , b是非volatile修饰的变量 , k则使用volatile修饰 。 所以语句3不能放在语句1、2前 , 也不能放在语句4、5后 。 但是语句1、2的顺序是不能保证的 , 同理 , 语句4、5也不能保证顺序 。
并且 , 执行到语句3的时候 , 语句1 , 2是肯定执行完毕的 , 而且语句12的执行结果对于语句345是可见的 。
volatile禁止指令重排序的原理是什么
首先要讲一下内存屏障 , 内存屏障可以分为以下几类:
  • 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读操作后插入LoadLoad屏障 , 在读操作后插入LoadStore屏障 。


在每个volatile写操作的前面插入一个StoreStore屏障 , 后面插入一个SotreLoad屏障 。


大概的原理就是这样 。
面试官:讲得还不错 , 基本上都讲到了 , 时间也不早了 , 今天的面试就到这吧 , 回去等通知吧~


推荐阅读