我不明白为什么这里需要局部变量:
public class FinalWrapper<T> {
public final T value;
public FinalWrapper(T value) {
this.value = value;
}
}
public class Foo {
private FinalWrapper<Helper> helperWrapper;
public Helper getHelper() {
FinalWrapper<Helper> tempWrapper = helperWrapper;
if (tempWrapper == null) {
synchronized(this) {
if (helperWrapper == null) {
helperWrapper = new FinalWrapper<Helper>(new Helper());
}
tempWrapper = helperWrapper;
}
}
return tempWrapper.value;
}
}
我从https://en.wikipedia.org/wiki/Double-checked_locking#Usage_in_Java获得此代码。如果我们没有这个局部变量,我们可以有什么问题?根据维基文章:
Java 5中最终字段的语义可用于安全地发布帮助程序对象而不使用volatile。正确性需要局部变量tempWrapper:只需使用helperWrapper进行空检查,并且由于Java内存模型允许的读取重新排序,return语句可能会失败。此实现的性能不一定比volatile实现更好。
提前致谢。
答案 0 :(得分:1)
要理解根本问题,让我们从代码中删除局部变量:
public class Foo {
private FinalWrapper<Helper> helperWrapper;
public Helper getHelper() {
if (helperWrapper == null) {
synchronized(this) {
if (helperWrapper == null) {
helperWrapper = new FinalWrapper<Helper>(new Helper());
}
}
}
return helperWrapper.value;
}
}
在这种情况下我们有三个读取:
问题在于,由于读取重新排序,第一次读取可以返回非null 值,第三次读取可以返回 null 。这意味着第三次读取发生在第一次读取之前,应该确保helperWrapper
被初始化...
添加局部变量可以解决问题,因为我们将helperWrapper
值分配给tempWrapper
,然后按{C}}读取的顺序无关紧要。如果它具有非null值,则它既用于null检查又用于return语句。
可能会发生这种情况,因为Java内存模型允许仅为优化目的重新排序操作。查看here的引用:
重新排序是什么意思?
在许多情况下访问程序变量 (对象实例字段,类静态字段和数组元素)可以 似乎以不同于指定的顺序执行 程序。编译器可以自由地使用自由命令 优化名称中的说明。处理器可以执行 在某些情况下,指令无序。数据可能是 在寄存器,处理器缓存和主存储器之间移动 不同于程序指定的顺序。
[...]
编译器,运行时和硬件应该密谋创建 as-if-serial语义的错觉,意味着在a 单线程程序,程序应该无法观察到 重新排序的影响。但是,重新排序可以发挥作用 错误同步的多线程程序,其中一个线程是 能够观察其他线程的影响,并且可能 检测变量访问对于其他线程是否可见 不同于程序中执行或指定的顺序。
[...]