多线程知识梳理(1):当我们谈到指令乱序的时候,在谈什么?
我喜欢先说结论。
程序里面的每行代码的执行顺序,有可能被编译器和CPU根据某种策略给打乱掉。目的是为了性能的提升,让指令的执行能尽可能的并行起来。
在Java代码运行过程中,有三处地方会发生指令乱序。
代码编译过程中,无论是javac将.java文件编译为.class文件的过程中,还是JIT动态编译的过程中,代码的执行顺序都有可能和你当时写的顺序不一样。
CPU执行过程中,CPU在执行指令的时候,并不一定会按照收到的指令顺序去执行,CPU为了让指令尽可能的并行执行,会打乱执行顺序。
内存乱序,也就是说,CPU的多核之间的指令顺序也不一致。比如CPU0执行的L0(假设这是一个读取操作,执行序号是0),再执行W1(假设这是一个写操作,执行序号是1),但是从另一个CPU1看起来,他可能先看到的是W1,再看到L0.
就像前面收的CPU为了使指令尽可能的并行起来,发明了流水线技术。但如果前后两个指令存在依赖关系,那么后一条语句就要等前一条完成后才能开始。
这里就要说到一个重要的原则:happens before原则
如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。
两个操作之间存在happens-before关系,并不意味着一定要按照happens-before原则制定的顺序来执行。如果重排序之后的执行结果与按照happens-before关系来执行的结果一致,那么这种重排序并不非法。
下面是happens-before原则规则:
CPU为了提高流水线的运行效率,就会做出比如:
但是,这里有一个地方非常重要,CPU虽然会按照自己的流水线去乱序执行给定的指令,但是从CPU对外表现来看却不是这样的。这得益于“重排序处理器”。
重排序处理器,会把各个指令的结果按照CPU接收到的指令顺序写入到store buffer、高速缓存或者内存区中
写缓冲器位于cpu核和高速缓存之间,对X86的架构来说,写缓冲器是FIFO(先进先出)的,因此并不会出现乱序的情况,但是对ARM\Power架构来说,写缓冲区并不能保证FIFO,因此可能会乱序。
cpu会将数据写入写缓冲器的过程是store,从高速缓存或者内存中读取数据是load。
写缓冲器和高速缓存执行store和load的过程都是按照处理器指示的顺序来的,处理的重拍处理器就是按程序的顺序来load和store的,但是其它的处理器看到的可能出现load和store是重排序的,也就是内存重排序。
这样的指令重排可能出现什么样的问题呢?我们简单举个例子:
Resource resource = null;
Boolean flag = false;
cpu0执行了
resource = loadResource();
flag = true;
cpu1执行的代码块是
while(!flag){
? // 一大堆业务逻辑
? // 等待信息等等
}
resource.excute();
但是由于内存重排导致,cpu1先看到了cpu0的写操作,也就是flag=true,这个时候代码块就跳出的while循环,开始执行resource.excute()方法。但是CPU1这个时候可能还没有看到cpu0的读取操作也就是resource=loadResource()方法,这个时候cpu1中的resource还是null,那再执行resource.execute()就会出现NPE。
原文:https://www.cnblogs.com/joimages/p/12762938.html