通过Java volatile进行一次性安全发布

时间:2016-10-23 07:30:52

标签: java volatile

本教程(link)关于Java的volatile声明是这个例子:

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);
        }
    }
}

据说后台线程是从数据库加载的,所以我认为作者意味着实例化new Flooble()需要花费大量时间。

引用:Without the theFlooble reference being volatile, the code in doWork() would be at risk for seeing a partially constructed Flooble as it dereferences the theFlooble reference.

怎么可能?我本来期待相反的。也就是说,我希望如果没有volatile声明,调用doWork方法的线程将有可能看到Flooble迟来或根本没有。

2 个答案:

答案 0 :(得分:3)

它可能是由编译器重新排序引起的。编译器可以内联Flooble的构造函数并更改其字段的初始化顺序并分配对theFlooble变量的引用。 声明theFlooble volatile会阻止此类重新排序。

请参阅https://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

答案 1 :(得分:1)

  

编译器可以内联Flooble的构造函数并更改其字段的初始化顺序并分配对Flolo变量的引用

我在博客,SO帖子以及其他许多地方都看到过这种情况,但它直接与java语言规范相矛盾:

https://docs.oracle.com/javase/specs/jls/se8/html/jls-12.html#jls-12.5

这实质上表明,除非有任何异常,否则构造函数将在返回引用之前完成。如果在构造对象之前没有引用,则无法在任何位置看到部分构造的对象。如果您内联构造函数,最好确保没有其他人可以看到该引用,或者您违反了规范。

我见过的一个程序集反编译似乎表明这是由一个古老的赛门铁克JIT编译器生成的 - 不完全正式。

如果有人可以解释为什么现代的,批准的,OpenJDK编译器可以重新排序写入以暴露对部分创建的对象的引用,以及为什么这不符合规范,我会咬,但直到,这一切看起来像一堆基于其他编译器和规范经验的挥手概括(并且,请注意,我并不是指通过将此指针泄露到外部类来揭示部分构造的对象 - 如果你这样做,你只会要责备自己。)