jvm重排序

Posted xiaowater

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了jvm重排序相关的知识,希望对你有一定的参考价值。

在执行程序时为了提高性能,编译器和处理器常常会对指令做重排序。重排序分三种类型:
  • 编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
  • 指令级并行的重排序。现代处理器采用了指令级并行技术(Instruction-Level Parallelism, ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
  • 内存系统的重排序。由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。
 
从java源代码到最终实际执行的指令序列,会分别经历下面三种重排序:
技术分享图片
上述的1属于编译器重排序,2和3属于处理器重排序。
 
as-if-serial语义的意思指:不管怎么重排序(编译器和处理器为了提高并行度),(单线程)程序的执行结果不能被改变。编译器,runtime 和处理器都必须遵守as-if-serial语义。
为了遵守as-if-serial语义,编译器和处理器不会对存在数据依赖关系的操作做重排序,因为这种重排序会改变执行结果。但是,如果操作之间不存在数据依赖关系,这些操作可能被编译器和处理器重排序。
 
技术分享图片
intra-thread semantics,请看下面的示意图

 

技术分享图片
多线程并发执行示意图
技术分享图片
两个办法来实现线程安全的延迟初始化:
  1. 不允许2和3重排序(声明对象的引用为volatile后,在多线程环境中将会被禁止重排序)
  2. 允许2和3重排序,但不允许其他线程“看到”这个重排序。(类初始化)
 
JVM在类的初始化阶段(即在Class被加载后,且被线程使用之前),会执行类的初始化。在执行类的初始化期间,JVM会去获取一个锁。这个锁可以同步多个线程对同一个类的初始化(Initialization On Demand Holder idiom)
技术分享图片
 
 

代码层面重排序

as-if-serial语义的意思指:不管怎么重排序(编译器和处理器为了提高并行度),(单线程)程序的执行结果不能被改变。
技术分享图片
 
flag变量是个标记,用来标识变量a是否已被写入。这里假设有两个线程A和B,A首先执行writer()方法,随后B线程接着执行reader()方法。线程B在执行操作4时,能否看到线程A在操作1对共享变量a的写入?
答案是:不一定能看到。由于操作1和操作2没有数据依赖关系,编译器和处理器可以对这两个操作重排序

 

在一些32位的处理器上,如果要求对64位数据的读/写操作具有原子性,会有比较大的开销。为了照顾这种处理器,java语言规范鼓励但不强求JVM对64位的long型变量和double型变量的读/写具有原子性。当JVM在这种处理器上运行时,会把一个64位long/ double型变量的读/写操作拆分为两个32位的读/写操作来执行。这两个32位的读/写操作可能会被分配到不同的总线事务中执行,此时对这个64位变量的读/写将不具有原子性
 

以上是关于jvm重排序的主要内容,如果未能解决你的问题,请参考以下文章

JVM的重排序

JVM技术专题 深入分析字节码指令重排序技术「原理篇」

Java指令重排序

指令重排序

JVM学习--内存模型可见性指令重排序

98%的程序员,都没有研究过JVM重排序和顺序一致性