我正在读这篇文章:https://en.m.wikipedia.org/wiki/Double-checked_locking
在 Java 中的用法部分,最后一个示例:
Java 5 中 Final 字段的语义可用于安全地发布辅助对象,而无需使用 volatile:
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;
}
}
为了保证正确性,需要局部变量 tempWrapper:简单地使用 helperWrapper 进行 null 检查,并且 return 语句可能会由于 Java 内存模型允许的读取重新排序而失败。[14]此实现的性能不一定比易失性实现更好。
为什么需要
tempWrapper
才能保证正确性?我不能将其删除并替换为helperWrapper
吗?据我了解,在构造函数中初始化最终字段 FinalWrapper<Helper>
之前,对 value
创建的对象的引用不会转义到其他线程。如果其他线程将 helperWrapper
读取为非空,则它必须具有正确的 value
值。
如果你直接在 return 语句中使用 helperWrapper ,它可能看起来像这样:
public Helper getHelper() {
if (helperWrapper == null) {
synchronized (this) {
if (helperWrapper == null) {
helperWrapper = new FinalWrapper<Helper>(new Helper());
}
}
}
return helperWrapper.value; // This could lead to seeing an uninitialized Helper
}
使用局部变量 tempWrapper 可以避免 Java 内存模型下内存可见性和重新排序的潜在问题。它保证一旦分配了 tempWrapper,对完全构造的 Helper 对象的引用始终可以安全使用。这就是为什么它对于正确性至关重要,即使乍一看似乎是多余的。