JVM的指令乱序性

464 阅读2分钟

这是我参与11月更文挑战的第22天,活动详情查看:2021最后一次更文挑战

在java代码执行的过程当中,如果两个语句操作之间不存在依赖关系,是有可能会被乱序执行的,这是因为指令重排序的原因。

这也意味着,一个对象的创建在多线程情况下是可能有问题的。

public class InstanceDemo {
    private static InstanceDemo INSTANCE;
​
    private InstanceDemo() {
    }
    public static InstanceDemo getInstance() {
        if (INSTANCE == null) {
            //双重检查
            synchronized (Mgr06.class) {
               if(INSTANCE == null) {
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    INSTANCE = new Mgr06();
               }
            }
        }
        return INSTANCE;
    }
​
    public void m() {
        System.out.println("m");
    }
​
    public static void main(String[] args) {
        for(int i=0; i<100; i++) {
            new Thread(()->{
                System.out.println(InstanceDemo.getInstance().hashCode());
            }).start();
        }
    }
}

这是单例模式中double check的典型实现

分析原因

public class CommandDisOrderTest001  {
    public static void main(String[] args) {
     Object object =new Object();
    }
}

以下为创建一个Object对象的指令集

image-20200813145322048.png

指令在栈中的执行的流程如下图

newObject.jpg

0 new #2 <java/lang/Object> 3 dup 4 invokespecial #1 <java/lang/Object.> 7 astore_1 8 return

  1. new 指令是向内存申请这个对象需要的空间,然后给对象赋予默认
  2. dup指令 是将在内存中的对象复制一份并且放在栈顶
  3. invokespecial 指令 是执行对象的初始化方法
  4. 把栈中的对象的引用赋予给局部变量表中的object

这样看是没问题的,但如果发生了指令重排序,指令7先执行,指令4后执行的呢? 那是不是意味着某个时刻是有可能得到一个半初始化状态的对象呢(只有默认值)?

回到之前的单例代码中,看INSTANCE = new InstanceDemo();这句话,如果指令被重排序,那么在多线程情况下,另外一个线程很可能会得到一个半初始化状态的对象!

也就是说代码在执行的过程并非如你想象,是具有一定顺序的执行流程

解决方案

有没有解决方法? 自然是有的,只需要给属性值赋予volatile的修饰,就可以避免指令重排序了。

private static volatile InstanceDemo INSTANCE;