1、重排序的类别

为了提升性能编译器和CPU常常会对指令重排序。

点击查看【processon】

  • 编译器优化重排序:编译器在不改变程序执行结果的情况下,为了提升效率,对指令进行乱序的编译。例如在代码中A操作需要获取其他资源而进入等待的状态,而A操作后面的代码跟其没有依赖关系,如果编译器一直等待A操作完成再往下执行的话效率要慢的多,所以可以先编译后面的代码,这样的乱序可以提升不小的编译速度。
  • 指令级并行重排序:处理器在不影响程序执行结果的情况下,将多条指令重叠在一起执行,同样也是为了提升效率。
  • 内存系统重排序:这个跟之前两个不同的是,其为伪重排序,也就是说只是看起来像在乱序执行而已。对于现代的处理器来说,在CPU和主内存之间都具备一个高速缓存,高速缓存的作用主要为减少CPU和主内存的交互(CPU的处理速度要快的多),在CPU进行读操作时,如果缓存没有的话从主内存取,而对于写操作都是先写在缓存中,最后再一次性写入主内存,原因是减少跟主内存交互时CPU的短暂卡顿,从而提升性能,但是延时写入可能会导致一个问题——数据不一致

2、As-if-Serial规则

编译器和cpu都需要遵守As-if-Serial规则。
As-if-Serial规则是指:无论如何重排序,都必须保证代码在单线程下运行正确

为了遵守As-if-Serial规则,编译器和CPU不会对存在数据依赖关系的操作进行重排序,因为这种重排序会改变执行结果。但是如果指令间不存在数据依赖关系,这些指令可能会被编译器和CPU重排序。

3、内存屏障

内存屏障又称之为内存栅栏(Memory Fences),是让一个CPU高速缓存的内存状态对其他CPU内核可见的一项技术,也是一项保障跨CPU内核有序执行指令的技术。
作用:

  1. 阻止屏障两侧的指令重排序,先于屏障的指令必须先执行,后于屏障的指令后执行
  2. 强制把写缓冲区/高速缓存中的脏数据等写回主内存,让缓存中相应的数据失效。

JMM提供了自己的内存屏障指令,要求JVM编译器实现这些指令,禁止特定类型的编译器和CPU重排序。


内存屏障分为写屏障(Store Barrier)、读屏障(Load Barrier)和全屏障(Full Barrier)

  • 读屏障(Load Barrier 在指令前插入Load Barrier,可以让高速缓存中的数据失效,强制从新从主内存加载数据,并且读屏障会告诉CPU和编译器,先于这个屏障的指令必须先执行。
  • 写屏障(Store Barrier)在指令后插入Store Barrier,能让写入缓存中的最新数据更新写入主内存,让其他线程可见,并且写屏障会告诉CPU和编译器,后于这个屏障的指令必须后执行。
  • 全屏障(Full Barrier)全屏障是一种全能型屏障,具备读屏障和写屏障的能力。