Java内存模型与指令重排

编译器优化重排

Happen-Before规则

原子性

原子性是指一个操作是不可中断的. 即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其它线程干扰. 例如CPU中的一些指令, 属于原子性的,又或者变量直接赋值操作(i = 1), 也是原子性的, 即使有多个线程对i赋值, 相互也不会干扰.

而如i++, 则不是原子性的, 因为他实际上i = i + 1, 若存在多个线程操作i, 结果将不可预期.

Java内存模型与指令重排

有序性

有序性是指在单线程环境中, 程序是按序依次执行的.

而在多线程环境中, 程序的执行可能因为指令重排而出现乱序, 下文会有详细讲述.

class OrderExample {
        int a = 0;
        boolean flag = false;

public void writer() {
            // 以下两句执行顺序可能会在指令重排等场景下发生变化
            a = 1;
            flag = true;
        }

public void reader() {
            if (flag) {
                int i = a + 1;
                ……
            }
        }
    }

Java内存模型与指令重排

可见性

可见性是指当一个线程修改了某一个共享变量的值,其他线程是否能够立即知道这个修改.

会有多种场景影响到可见性:

CPU指令重排

多条汇编指令执行时, 考虑性能因素, 会导致执行乱序, 下文会有详细讲述.

硬件优化(如写吸收,批操作)

cpu2修改了变量T, 而cpu1却从高速缓存cache中读取了之前T的副本, 导致数据不一致.

Java内存模型与指令重排

编译器优化

主要是Java虚拟机层面的可见性, 下文会有详细讲述.

指令重排

指令重排是指在程序执行过程中, 为了性能考虑, 编译器和CPU可能会对指令重新排序.

CPU指令重排

一条汇编指令的执行是可以分为很多步骤的, 分为不同的硬件执行

取指 IF

译码和取寄存器操作数 ID

执行或者有效地址计算 EX (ALU逻辑计算单元)

存储器访问 MEM

写回 WB (寄存器)

既然指令可以被分解为很多步骤, 那么多条指令就不一定依次序执行.

因为每次只执行一条指令, 依次执行效率太低了, 假设上述每一个步骤都要消耗一个时钟周期, 那么依次执行的话, 一条指令要5个时钟周期, 两条指令要占用10个时钟周期, 三条指令消耗15个时钟.

Java内存模型与指令重排

而如果硬件空闲即可执行下一步, 类似于工厂中的流水线, 一条指令要5个时钟周期, 两条指令只需要6个时钟周期, 因为是错位流水执行, 三条指令消耗7个时钟.

Java内存模型与指令重排

举个例子 A = B + C, 需要如下指令

指令1 : 加载B到寄存器R1中

指令2 : 加载C到寄存器R2中

指令3 : 将R1与R2相加, 得到R3

指令4 : 将R3赋值给A

注意下图红色框选部分, 指令1, 2独立执行, 互不干扰.

指令3依赖于指令1, 2加载结果, 因此红色框选部分表示在等待指令1, 2结束.

待指令1, 2都已经走完MEM部分, 数据加载到内存后, 指令3继续执行计算EX.

同理指令4需要等指令3计算完, 才可以拿到R3, 因此也需要错位等待.

Java内存模型与指令重排

再来看一个复杂的例子

a = b + c

d = e - f

具体指令执行步骤如图, 不再赘述, 与上图类似, 在执行过程中同样会出现等待.

Java内存模型与指令重排

这边框选的X统称一个气泡, 有没有什么方案可以削减这类气泡呢.

答案自然是可以的, 我们可以在出现气泡之前, 执行其他不相干指令来减少气泡.

例如可以将第五步的加载e到寄存器提前执行, 消除第一个气泡, 

同理将第六步的加载f到寄存器提前执行, 消除第二个气泡.

Java内存模型与指令重排

经过指令重排后, 整个流水线会更加顺畅, 无气泡阻塞执行.

Java内存模型与指令重排

原先需要14个时钟周期的指令, 重排后, 只需要12个时钟周期即可执行完毕.

指令重排只可能发生在毫无关系的指令之间, 如果指令之间存在依赖关系, 则不会重排.

如 指令1 : a = 1 指令2: b = a - 1, 则指令1, 2 不会发生重排.

编译器优化

主要指jvm层面的, 如下代码, 在jvm client模式很快就跳出了while循环, 而在server模式下运行, 永远不会停止.

/**
 * Created by Administrator on 2018/5/3/0003.
 */
public class VisibilityTest extends Thread {
    private boolean stop;

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/7d9ac423d681217e025e58f6ccda59f9.html