代码通俗易懂的讲解 volatile 禁止指令重排原理

1,108 阅读2分钟

volatile 禁止指令重排

本文介绍下volatile禁止指令重排的背景及最终效果。

volatile 可见性保证

我们都知道 volatile 会保证读写修饰的变量时会将对应变量同步给主存。

但是实际上, volatile 修饰的变量不仅保证自身,还会保证其他局部变量的可见性。

以下结合例子讲解下。


public class MyClass {
    private int years;
    private int months;
    private volatile int days;

    // 读主存的时候,会把后边的变量一起顺带读出来
    public int totalDays() {
        int total = this.days;
        total += months * 30;
        total += years * 365;
        return total;
    }

    // 写主存的时候, 会把前边的变量一起顺带写进去
    public void update(int years, int months, int days){
        this.years  = years;
        this.months = months;
        this.days   = days;
    }
}

Reordinrg 指令重排的挑战

指令重排前的代码

int a = 1;
int b = 2;

a++;
b++;

可能的重排后代码

// 估计是为了读写操作一起,减少内存缺页率
int a = 1;
a++;

int b = 2;
b++;

对volatile可见性保证的影响


// 指令重排前,写主存的时候, 会把前边的变量一起写进去
public void update(int years, int months, int days){
    this.years  = years;
    this.months = months;
    this.days   = days;
}

// 指令重排后,写主存的时候, 本来该顺带写的变量不写了
public void update(int years, int months, int days){
    this.days   = days;
    this.years  = years;
    this.months = months;
}

hanppens-before 发生前保证

为了保证 vaolatile 对其他变量可见性的保证 规则, volatile 的 happens-before 规定对指令重排的限制。

读写其他变量如果本身在写volatile变量之前的, 禁止重排到写volatile变量之后。

读写其他变量如果本身在读volatile变量之后的, 禁止重排到读volatile变量之前。

据说 happens-before 是 JSR-133 的规范质疑,内存屏障是 CPU 的指令。

前者是目的, 后者是实现目的的手段。

本文主要参考

tutorials.jenkov.com/java-concur…

mp.weixin.qq.com/s/DZkGRTan2…