大多数现代微处理器都会采用将指令乱序执行(out-of-order execution,简称OoOE或OOE)的方法,
在条件容许的状况下,直接运行当前有能力当即执行的后续指令,避开获取下一条指令所需数据时形成的等待。
经过乱序执行的技术,处理器能够大大提升执行效率。 除了处理器,常见的Java运行时环境的JIT编译器也会作指令重排序操做,即生成的机器指令与字节码指令顺序不一致。
As-if-serial语义的意思是,全部的动做(Action)均可觉得了优化而被重排序,可是必须保证它们重排序后的结果和程序代码自己的应有结果是一致的。
Java编译器、运行时和处理器都会保证单线程下的as-if-serial语义。
ps:即指令好像是连续的,是对这种执行效果特性的一个说法。java
为了保证这一语义,重排序不会发生在有数据依赖的操做之中。缓存
计算机系统中,为了尽量地避免处理器访问主内存的时间开销,处理器大多会利用缓存(cache)以提升性能。
即缓存中的数据与主内存的数据并非实时同步的,各CPU(或CPU核心)间缓存的数据也不是实时同步的。
这致使在同一个时间点,各CPU所看到同一内存地址的数据的值多是不一致的。
从程序的视角来看,就是在同一个时间点,各个线程所看到的共享变量的值可能是不一致的。
有的观点会将这种现象也视为重排序的一种,命名为“内存系统重排序”。
由于这种内存可见性问题形成的结果就好像是内存访问指令发生了重排序同样。
(执行了殊不知道执行了和觉得执行了却重排序没有执行形成相同效果)
Java的目标是成为一门平台无关性的语言,即Write once, run anywhere. 可是不一样硬件环境下指令重排序的规则不尽相同。 例如,x86下运行正常的Java程序在IA64下就可能获得非预期的运行结果。 为此,JSR-1337制定了Java内存模型(Java Memory Model, JMM),旨在提供一个统一的可参考的规范,屏蔽平台差别性。 从Java 5开始,Java内存模型成为Java语言规范的一部分。
根据Java内存模型中的规定,能够总结出如下几条happens-before规则。app
(ps:内存模型即经过运行环境把一些可见性和重排序问题统一成一个标准描述)函数
Happens-before的先后两个操做不会被重排序且后者对前者的内存可见。post
程序次序法则: 线程中的每一个动做A都happens-before于该线程中的每个动做B,其中,在程序中,全部的动做B都能出如今A以后。 监视器锁法则: 对一个监视器锁的解锁 happens-before于每个后续对同一监视器锁的加锁。 volatile变量法则:对volatile域的写入操做happens-before于每个后续对同一个域的读写操做。 线程启动法则: 在一个线程里,对Thread.start的调用会happens-before于每一个启动线程的动做。 线程终结法则:线程中的任何动做都happens-before于其余线程检测到这个线程已经终结、或者从Thread.join调用中成功返回,或Thread.isAlive返回false。 中断法则: 一个线程调用另外一个线程的interrupt happens-before于被中断的线程发现中断。 终结法则: 一个对象的构造函数的结束happens-before于这个对象finalizer的开始。 传递性: 若是A happens-before于B,且B happens-before于C,则A happens-before于C
Happens-before关系只是对Java内存模型的一种近似性的描述,它并不够严谨,但便于平常程序开发参考使用,性能
关于更严谨的Java内存模型的定义和描述,请阅读JSR-133原文或Java语言规范章节17.4。优化
除此以外,Java内存模型对volatile和final的语义作了扩展。this
对volatile语义的扩展保证了volatile变量在一些状况下不会重排序,volatile的64位变量double和long的读取和赋值操做都是原子的。
对final语义的扩展保证一个对象的构建方法结束前,全部final成员变量都必须完成初始化(前提是没有this引用溢出)。
(ps:没有理解final的意思)url
Java内存模型关于重排序的规定,总结后以下表所示。(ps:下表没看懂)
内存屏障(Memory Barrier,或有时叫作内存栅栏,Memory Fence)是一种CPU指令,用于控制特定条件下的重排序和内存可见性问题。
Java编译器也会根据内存屏障的规则禁止重排序。
内存屏障能够被分为如下几种类型:
LoadLoad 屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操做要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操做执行前,保证Store1的写入操做对其它处理器可见。
LoadStore 屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操做被刷出前,保证Load1要读取的数据被读取完毕。
StoreLoad 屏障:对于这样的语句Store1; StoreLoad; Load2,在Load2及后续全部读取操做执行前,保证Store1的写入对全部处理器可见。
它的开销是四种屏障中最大的。在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能。
有的处理器的重排序规则较严,无需内存屏障也能很好的工做,Java编译器会在这种状况下不放置内存屏障。
为了实现上一章中讨论的JSR-133的规定,Java编译器会这样使用内存屏障。(ps:下表没看懂)