如果另一个线程要在“期间”构造期间检查变量,我相信它可能会(由于内存模型中的怪癖)看到一个部分初始化的对象。新的(从Java 5开始)内存模型意味着,在对象对其他线程可见之前,任何最终字段都应该设置为它们的值(只要对新创建对象的引用不会以任何其他方式从构造函数中转义),但除此之外没有太多保证。someObject
基本上,如果没有适当的锁定(或静态初始化器等提供的保证),请不要共享数据:)说真的,内存模型非常棘手,一般的无锁编程也是如此。尽量避免这种情况成为一种可能性。
从逻辑上讲,赋值发生在构造函数运行之后 - 因此,如果您从同一线程观察变量,则在构造函数调用期间它将为 null。但是,正如我所说,内存模型有些奇怪。
编辑:出于双重检查锁定的目的,如果您的字段是并且如果您使用的是Java 5或更高版本,则可以解决此问题。在Java 5之前,内存模型还不够强大。不过,您需要获得完全正确的模式。有关更多详细信息,请参阅有效 Java,第 2 版,第 71 项。volatile
编辑:这是我反对Aaron的内联在单个线程中可见的理由。假设我们有:
public class FooHolder
{
public static Foo f = null;
public static void main(String[] args)
{
f = new Foo();
System.out.println(f.fWasNull);
}
}
// Make this nested if you like, I don't believe it affects the reasoning
public class Foo
{
public boolean fWasNull;
public Foo()
{
fWasNull = FooHolder.f == null;
}
}
我相信这将永远报告。从第15.26.1节开始:true
否则,需要执行三个步骤:
- 首先,计算左侧操作数以生成变量。如果此计算突然完成,则赋值表达式由于同样的原因突然完成;不计算右侧操作数,并且不会进行赋值。
- 否则,将计算右侧操作数。如果此计算突然完成,则赋值表达式由于同样的原因突然完成,并且不会发生赋值。
否则,右侧操作数的值将转换为左侧变量的类型,进行值集转换 (§5.1.13) 到适当的标准值集(不是扩展指数值集),并将转换结果存储到变量中。
然后从第17.4.5节开始:
可以按“发生前”关系对两个操作进行排序。如果一个操作发生在另一个操作之前,则第一个操作对第二个操作可见并排序。
如果我们有两个动作 x 和 y,我们写 hb(x, y) 来指示 x 发生在 y 之前。
- 如果 x 和 y 是同一线程的操作,并且 x 在程序顺序上位于 y 之前,则 hb(x, y)。
- 从对象构造函数的末尾到该对象的终结器 (§12.6) 的开头,存在一个“发生前”边缘。
- 如果一个动作 x 与一个后续的动作 y 同步,那么我们也有 hb(x, y)。
- 如果 hb(x, y) 和 hb(y, z),则 hb(x, z)。
应该注意的是,两个操作之间存在“发生之前”关系并不一定意味着它们在实现中必须按该顺序发生。如果重新排序产生与合法执行一致的结果,则不违法。
换句话说,即使在单个线程中发生奇怪的事情也是可以的,但这一定是不可观察的。在这种情况下,差异是可以观察到的,这就是为什么我认为它是非法的。