本教程(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迟来或根本没有。
答案 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编译器可以重新排序写入以暴露对部分创建的对象的引用,以及为什么这不符合规范,我会咬,但直到,这一切看起来像一堆基于其他编译器和规范经验的挥手概括(并且,请注意,我并不是指通过将此指针泄露到外部类来揭示部分构造的对象 - 如果你这样做,你只会要责备自己。)