在ibm中有一个关于volatile的article,解释使我感到困惑,下面是本文的一个示例及其解释:

public class BackgroundFloobleLoader {
    public volatile Flooble theFlooble;

    public void initInBackground() {
        // do lots of stuff
        theFlooble = new Flooble();  // this is the only write to theFlooble
    }
}

public class SomeOtherClass {
    public void doWork() {
        while (true) {
            // do some stuff...
            // use the Flooble, but only if it is ready
            if (floobleLoader.theFlooble != null)
                doSomething(floobleLoader.theFlooble);
        }
    }
}



  如果没有使theFlooble引用不稳定,则doWork()中的代码将因为取消引用theFlooble引用而面临部分构造的Flooble的风险。


怎么理解?为什么不使用volatile,我们可以使用部分构造的Flooble对象?谢谢!

最佳答案

没有volatile,您可能会看到部分构造的对象。例如。考虑这个Flooble对象。

public class Flooble {
    public int x;
    public int y;

    public Flooble() {
       x = 5;
       y = 1;
    }
}

public class SomeOtherClass {
    public void doWork() {
    while (true) {
        // do some stuff...
        // use the Flooble, but only if it is ready
        if (floobleLoader.theFlooble != null)
            doSomething(floobleLoader.theFlooble);
    }

    public void doSomething(Flooble flooble) {
        System.out.println(flooble.x / flooble.y);
    }
}


}

如果没有volatile,则不能确保doSomething方法看到51的值xy。例如,它可以看到x == 5y == 0导致被零除。

当您执行此操作theFlooble = new Flooble()时,将发生三个写入:


tmpFlooble.x = 5
tmpFlooble.y = 1
theFlooble = tmpFlooble


如果这些写操作按此顺序进行,则一切正常。但是如果没有volatile,编译器可以自由地对这些写入进行重新排序,并根据需要执行它们。例如。首先是点3,然后是点1和2。

这实际上一直在发生。编译器确实对写进行了重新排序。这样做是为了提高性能。

该错误很容易以下列方式发生:

线程A从类initInBackground()执行BackgroundFloobleLoader方法。编译器会对写入进行重新排序,因此在执行Flooble()的正文(已设置xy的正文)之前,线程A首先执行theFlooble = new Flooble()。现在,theFlooble指向一个floble实例,其xy0。在线程A继续之前,其他一些线程B执行类doWork()的方法SomeOtherClass。此方法使用当前值doSomething(floobleLoader.theFlooble)调用方法theFlooble。在此方法中,theFlooble.x除以theFlooble.y导致被零除。线程B由于未捕获的异常而结束。线程A继续并设置theFlooble.x = 5theFlooble.y = 1

当然,这种情况不会在每次运行时都发生,但是根据Java的规则,可能会发生。

10-08 13:43