【发布时间】:2016-08-08 22:29:53
【问题描述】:
我正在尝试解决一个简单的问题,却陷入了 Java 内存模型的兔子洞。
编写包含非最终引用字段的 Java 类的最简单和/或最有效(此处为判断调用)但无竞争(根据 JMM 精确定义)的方法是什么哪个在构造函数中被初始化为非空值并且随后永远不会改变,这样任何其他线程对该字段的后续访问都不能看到非空值?
破碎的起始例子:
public class Holder {
private Object value;
public Holder(Object value) {
if (value == null)
throw NullPointerException();
this.value = value;
}
public Object getValue() { // this could return null!
return this.value;
}
}
根据this post,标记字段volatile 甚至不起作用!
public class Holder {
private volatile Object value;
public Holder(Object value) {
if (value == null)
throw NullPointerException();
this.value = value;
}
public Object getValue() { // this STILL could return null!!
return this.value;
}
}
这是我们能做的最好的吗??
public class Holder {
private Object value;
public Holder(Object value) {
if (value == null)
throw NullPointerException();
synchronized (this) {
this.value = value;
}
}
public synchronized Object getValue() {
return this.value;
}
}
好的,这个怎么样?
public class Holder {
private Object value;
public Holder(Object value) {
if (value == null)
throw NullPointerException();
this.value = value;
synchronized (this) { }
}
public synchronized Object getValue() {
return this.value;
}
}
旁注:related question 询问如何在不使用任何 volatile 或同步的情况下执行此操作,这当然是不可能的。
【问题讨论】:
-
AtomicReference怎么样? -
第一个示例中的方法
getValue()在哪种情况下可以返回null?此外,如果您打算在构造函数中只分配一次值,然后就读取它 - 在这种情况下,听起来您正在寻找一个不可变对象 - 为什么不将其声明为final? (我看到你写“非最终”只是因为你没有提供好的动机......) -
getValue()不能返回 null,除非构造函数中有this转义,但没有。否则对getValue()的任何调用都必须跟在整个构造函数之后,要么在同一个线程中,要么在构造函数返回后由同一个线程启动的线程中,并且JLS #17.4;5 保证hb(x,y)如果 x 和 y 是同一线程中的连续动作。 -
如果它在构造函数中被初始化,并且从未改变,为什么不让它成为final呢?
-
@Archie 非常正确。但是
this引用只存在于构造函数返回后,将其交给另一个线程的动作将构成JLS引用中的y。