关于java:3Volatile禁止指令重排

35次阅读

共计 1218 个字符,预计需要花费 4 分钟才能阅读完成。

计算机在执行程序时,为了进步性能,编译器和处理器经常会对指令重排,个别分为以下三种:

源代码 -> 编译器优化的重排 -> 指令并行的重排 -> 内存零碎的重排 -> 最终执行指令

处理器在进行重排序时必须要思考指令之间的 数据依赖性

  • 多线程环境中线程交替执行,因为编译器优化重排的存在,两个线程中应用的变量是否保障一致性是无奈确定的,后果无奈预测。

指令重排——example 1

public void mySort() {
    int x = 11;    
    int y = 12;
    x = x + 5;
    y = x * x;
}

依照失常单线程环境,执行程序是 1234。然而在多线程环境中,可能呈现以下的程序:2134、1324

上述的过程就能够当作指令的重排,即外部执行程序,和咱们的代码程序不一样

然而指令重排也是有限度的,不会呈现上面的程序:4321

因为步骤 4,须要依赖 y 的申明,以及 x 的申明,故因为存在数据依赖,无奈首先执行

例子

先定义:int a,b,x,y = 0

线程 1 线程 2
x = a y = b
b = 1 a = 2

后果 x =0,y=0

因为下面的代码,不存在数据的依赖性,因而编译器可能对数据进行重排。

线程 1 线程 2
b = 1 a = 2
x = a y = b

后果 x =2,y=1

  • 这就是导致重排后,后果和最开始的不一样,因而为了避免这种后果呈现,volatile 就规定禁止指令重排,为了保证数据的一致性。

指令重排 – example 2

public class ResortSeqDemo {
    int a= 0;
    boolean flag = false;

    public void method01() {
        a = 1;
        flag = true;
    }

    public void method02() {if(flag) {
            a = a + 5;
            System.out.println("reValue:" + a);
        }
    }
}

如果失常顺序调用,别离调用 method01()和 method02(),输入后果应该是 a = 6


然而如果在多线程的环境下,因为办法 1 和办法 2 不存在数据依赖的问题,因而原先的程序可能是

a = 1;
flag = true;

a = a + 5;
System.out.println("reValue:" + a);

然而通过编译器,指令或内存的重排后,可能会呈现:

flag = true;

a = a + 5;
System.out.println("reValue:" + a);

a = 1;

也就是先执行 flag = true 后,另外一个线程马上调用办法 2,满足 flag 的判断,最终让 a + 5,后果为 5,这样同样呈现了数据不统一的问题。这样就须要通过 volatile 来润饰,来保障线程安全性。

Volatile 针对指令重排做了啥

Volatile 实现禁止指令重排优化,从而防止了多线程环境下程序呈现乱序执行的景象

首先理解一个概念,内存屏障(Memory Barrier)又称内存栅栏,是一个 CPU 指令,它的作用有两个:

  • 保障特定操作的程序
  • 保障某些变量的内存可见性(利用该个性实现 volatile 的内存可见性)

也就是过在 Volatile 的写 和 读的时候,退出屏障,防止出现指令重排的。

正文完
 0