您的位置:

指令重排序:原理与应用

一、指令重排序案例

指令重排序是CPU为了提高指令执行效率的一种优化方式,它会将指令按照一定规则重新排序。以下是一个指令重排序的经典案例:

public class DCLSingleton {
    private static volatile DCLSingleton instance;
    private DCLSingleton() {}

    public static DCLSingleton getInstance() {
        if (instance == null) {
            synchronized (DCLSingleton.class) {
                if (instance == null) {
                    instance = new DCLSingleton();
                }
            }
        }
        return instance; 
    }
}

在并发情况下,由于指令重排序的存在,可能会引发DCL(Double-Check Locking)失败的问题,使得单例模式失效。

二、指令重排序和JMM的关系

Java内存模型(Java Memory Model,JMM)是Java平台中各种编译器的内存模型的抽象。为了支持跨平台的Java编程,JMM规范了不同线程之间的内存可见性、数据原子性、指令重排序等问题,并提供了一套完整的内存屏障(Memory Barrier)语义规范。

在JMM的规范下,JVM对于指令重排序有以下保证

  • 单线程中,按照指令出现的顺序执行。
  • 多线程中,无法保证按照指令出现的顺序执行,但会保证在JMM规定下的“内存屏障”出现的时候,程序执行满足JMM的需求。

三、指令重排序什么意思

指令重排序是CPU为了提高指令执行效率而进行的一种优化方式。由于现代CPU都采用了流水线技术,当一条指令执行完后,下一条指令随即进入流水线被执行。而在执行过程中,如果出现了数据相关等问题,就需要停顿流水线等待数据,这样会降低CPU效率。为了尽可能避免出现这种情况,CPU引入了指令重排序技术,将原来的指令序列重新排序,尽可能保证流水线能够无延迟地执行指令。

四、指令重排序会有什么问题

虽然指令重排序能够提高系统的效率,但也可能引发一些隐患。最典型的例子是DCL。

在DCL的代码片段中,变量instance被声明为volatile,该关键字表示变量的修改对于其他线程都是可见的。但是如果在synchronized关键字后面的重排序过程中,创建对象的构造函数先执行,而在给instance赋值的过程中出现了重排序,则会引发DCL的失败。

五、指令重排单线程有问题

单线程在执行指令重排时,也存在隐患。以下这个例子可以说明这个问题:

public class Test {
    public static void main(String[] args) {
        int a = 0;
        boolean flag = false;
        a = 1;

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        if (a == 1 && !flag) {
            System.out.println("a==1");
        }
    }
}

在单线程中执行以上代码,有可能导致输出结果为空。这是因为JVM为了提高执行效率,可能会将if语句执行的代码片段与a=1这条语句进行重排序,导致程序逻辑错误。

六、代码示例

public class Test {
    public static void main(String[] args) {
        int a = 0;
        boolean flag = false;
        a = 1;

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        if (a == 1 && !flag) {
            System.out.println("a==1");
        }
    }
}

以上代码就体现了指令重排单线程的隐患。在多线程中,也要注意指令重排序可能引发的问题。