通过 Java volatile 一次性安全发布
one-time safe publication via a 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 的风险。
这可能是由编译器重新排序引起的。编译器可以内联 Flooble
的构造函数并更改其字段的初始化顺序并将引用分配给 theFlooble
变量。
声明 theFlooble
volatile 可防止此类重新排序。
见https://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
Compiler can inline Flooble's constructor and change order of initialization of its fields and assigning reference to theFlooble variable
我在博客、SO 帖子和许多其他地方看到过这个声明,但它直接与 java 语言规范相矛盾:
https://docs.oracle.com/javase/specs/jls/se8/html/jls-12.html#jls-12.5
本质上说,除非有任何异常,否则构造函数将在返回引用之前完成。如果在构造对象之前没有引用,则在任何地方都看不到部分构造的对象。如果您内联构造函数,您最好确保没有其他人可以看到该引用,否则您将违反规范。
我看到的一个程序集反编译似乎表明这是由一个古老的 Symantec JIT 编译器生成的 - 不完全是官方的。
如果有人能解释为什么现代的、经过批准的 OpenJDK 编译器可以重新排序写入以公开对部分创建的对象的引用,以及为什么这与规范不矛盾,我会坚持下去,但直到,这一切看起来像是一堆基于其他编译器和规范经验的挥手概括(注意,我并不是说通过将 this 指针泄漏到外部 class 来揭示部分构造的对象 - 如果你那样做,只能怪你自己。
在本教程 (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 的风险。
这可能是由编译器重新排序引起的。编译器可以内联 Flooble
的构造函数并更改其字段的初始化顺序并将引用分配给 theFlooble
变量。
声明 theFlooble
volatile 可防止此类重新排序。
见https://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
Compiler can inline Flooble's constructor and change order of initialization of its fields and assigning reference to theFlooble variable
我在博客、SO 帖子和许多其他地方看到过这个声明,但它直接与 java 语言规范相矛盾:
https://docs.oracle.com/javase/specs/jls/se8/html/jls-12.html#jls-12.5
本质上说,除非有任何异常,否则构造函数将在返回引用之前完成。如果在构造对象之前没有引用,则在任何地方都看不到部分构造的对象。如果您内联构造函数,您最好确保没有其他人可以看到该引用,否则您将违反规范。
我看到的一个程序集反编译似乎表明这是由一个古老的 Symantec JIT 编译器生成的 - 不完全是官方的。
如果有人能解释为什么现代的、经过批准的 OpenJDK 编译器可以重新排序写入以公开对部分创建的对象的引用,以及为什么这与规范不矛盾,我会坚持下去,但直到,这一切看起来像是一堆基于其他编译器和规范经验的挥手概括(注意,我并不是说通过将 this 指针泄漏到外部 class 来揭示部分构造的对象 - 如果你那样做,只能怪你自己。