什么是指令重排序
例子
1 | public class MemoryReorderingExample { |
在运行多次后,会出现(0,0)的结果。
以上很惊讶的结果,就是指令重排序导致的结果。
定义
指令重排序是指编译器或CPU为了优化程序的执行性能而对指令进行重新排序的一种手段,重排序会带来可见性问题,所以在多线程开发中必须要关注并规避重排序。
分类
编译器级别的指令重排
第一阶段,编译器重排序,就是在编译过程中,编译器根据上下文分析对指令进行重排序,目的是减少CPU和内存的交互,重排序之后尽可能保证CPU从寄存器或缓存行中读取数据。
在前面分析JIT优化中提到的循环表达式外提(Loop Expression Hoisting)就是编译器层面的重排序,从CPU层面来说,避免了处理器每次都去内存中加载stop,减少了处理器和内存的交互开销。
因为stop在单线程视角下,永远不会变化,所以编译器有理由把stop判断逻辑往前移动。
1 | while(true){ |
从而变成
1 | if(!stop){ |
运行期间的处理器指令重排
也分为两部分
并行指令集重排序
这是处理器优化的一种,处理器可以改变指令的执行顺序。
as-if-serial语义
as-if-serial表示所有的程序指令都可以因为优化而被重排序,但是在优化的过程中必须要保证是在单线程环境下,重排序之后的运行结果和程序代码本身预期的执行结果一致,Java编译器、CPU指令重排序都需要保证在单线程环境下的as-if-serial语义是正确的。
内存系统重排序
这是处理器引入Store Buffer缓冲区延时写入产生的指令执行顺序不一致的问
存储子系统重排序:在处理器严格依照程序顺序执行两个内存访问操作的情况下,在存储子系统的作用下其他处理器对这两个操作的感知顺序仍然可能与程序顺序不一致,即两个操作的执行顺序看起来好像发生了变化,这种现象就是存储子系统重排序。这只是一种现象而不是一种动作,它并没有真正对指令执行顺序进行调整,而只是造成了一种指令的执行顺序好像是被调整过一样的现象,其重排序对象是内存操作的结果。内存重排序也可能导致线程安全问题,比如那个s0和s1写入写缓冲器中,但是顺序发生变化,P1没有读到p2线程写入缓冲器中的,读的还是老的,就出现线程安 全问题了。