参考文档:html
https://tech.meituan.com/java-memory-reordering.htmljava
http://0xffffff.org/2017/02/21/40-atomic-variable-mutex-and-memory-barrier/缓存
内存可见性:http://blog.csdn.net/ty_laurel/article/details/52403718app
1、什么是重排序函数
重排序分为2种优化
经过调整代码中的指令顺序,在不改变代码语义的前提下,对变量访问进行优化。从而尽量的减小对寄存器的读取和存储,并充分复用寄存器。可是编译器对数据的依赖关系判断只能在单执行流内,没法判断其余执行流对竞争数据的依赖关系atom
流水线(Pipeline)和乱序执行是现代CPU基本都具备的特性。机器指令在流水线中经历取指、译码、执行、访存、写回等操做。为了CPU的执行效率,流水线都是并行处理的,在不影响语义的状况下。处理器次序(Process Ordering,机器指令在CPU实际执行时的顺序)和程序次序(Program Ordering,程序代码的逻辑执行顺序)是容许不一致的,即知足As-if-Serial特性。显然,这里的不影响语义依旧只能是保证指令间的显式因果关系,没法保证隐式因果关系。即没法保证语义上不相关可是在程序逻辑上相关的操做序列按序执行spa
as-if-serial语义:.net
全部的动做均可觉得了优化而被重排序,可是必须保证它们重排序后的结果和程序代码自己的应有结果是一致的。Java编译器、运行时和处理器都会保证单线程下的as-if-serial语义线程
为保证as-if-serial语义,Java异常处理机制也会为重排序作一些特殊处理。例如在下面的代码中,y = 0 / 0可能会被重排序在x = 2以前执行,为了保证最终不致于输出x = 1的错误结果,JIT在重排序时会在catch语句中插入错误代偿代码,将x赋值为2,将程序恢复到发生异常时应有的状态。这种作法的确将异常捕捉的逻辑变得复杂了,可是JIT的优化的原则是,尽力优化正常运行下的代码逻辑,哪怕以catch块逻辑变得复杂为代价,毕竟,进入catch块内是一种“异常”状况的表现
public class Reordering { public static void main(String[] args) { int x, y; x = 1; try { x = 2; y = 0 / 0; } catch (Exception e) { } finally { System.out.println("x = " + x); } } }
重排序知足happen before原则
2、什么是内存可见性
可见性:一个线程对共享变量值的修改,可以及时地被其余线程看到
共享变量:若是一个变量在多个线程的工做内存中都存在副本,那么这个变量就是这几个线程的共享变量
Java内存模型(JMM)
Java内存模型(Java Memory Model)描述了Java程序中各类变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取出变量这样的底层细节。
全部的变量都存储在主内存中。每一个线程都有本身独立的工做内存,里面保存该线程使用到的变量的副本(主内存中该变量的一份拷贝),如图
两条规定:
在这种模型下会存在一个现象,即缓存中的数据与主内存的数据并非实时同步的,各CPU(或CPU核心)间缓存的数据也不是实时同步的。这致使在同一个时间点,各CPU所看到同一内存地址的数据的值多是不一致
如何实现内存可见性:
要实现共享变量的可见性,必须保证两点
1)synchronized实现可见性
synchronized可以实现:
原子性(同步)
可见性
JMM关于synchronized的两条规定:
线程解锁前对共享变量的修改在下次加锁时对其余线程可见
线程执行互斥代码的过程
2)volatile实现可见性
volatile关键字:
可以保证volatile变量的可见性
不能保证volatile变量复合操做的原子
volatile如何实现内存的可见性:
在每一个volatile写操做前插入StoreStore屏障,在写操做后插入StoreLoad屏障
在每一个volatile读操做前插入LoadLoad屏障,在读操做后插入LoadStore屏障
线程写volatile变量的过程:
线程读volatile变量的过程:
synchronized vs volatile
3、内存屏障
内存屏障的做用:
硬件层的内存屏障分为两种:Load Barrier 和 Store Barrier即读屏障和写屏障
java内存屏障:
final语义中的内存屏障:
4、优化屏障
避免编译器的重排序优化操做,保证编译程序时在优化屏障以前的指令不会在优化屏障以后执行。这就保证了编译时期的优化不会影响到实际代码逻辑顺序
优化屏障告知编译器: 内存信息已经修改,屏障后的寄存器的值必须从内存中从新获取 必须按照代码顺序产生汇编代码,不得越过屏障