java 对象在构造过程中何时变为非空?
假设您正在创建一个 Java 对象,如下所示:
SomeClass someObject = null;
someObject = new SomeClass();
someObject 在什么时候变为非空? 是在 SomeClass()
构造函数运行之前还是之后?
为了澄清一点,假设另一个线程要在 SomeClass()
构造函数完成一半时检查 someObject
是否为 null,那么它是 null 还是非 null?
另外,如果 someObject
是这样创建的,会有什么区别:
SomeClass someObject = new SomeClass();
someObject
会为空吗?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
如果另一个线程要在“构建期间”检查 someObject 变量,我相信它可能会(由于内存模型中的怪癖)看到部分初始化的对象。 新的(从 Java 5 开始)内存模型意味着任何 final 字段都应该在该对象对其他线程可见之前设置为其值(只要对新创建的对象的引用不存在)以任何其他方式从构造函数中逃脱)但除此之外没有太多保证。
基本上,如果没有适当的锁定(或静态初始化器等给出的保证),请勿共享数据:) 说实话,内存模型非常棘手,就像一般的无锁编程一样。 尽量避免这种情况成为可能。
从逻辑术语来看,赋值发生在构造函数运行之后 - 因此,如果您从同一个线程观察变量,则在构造函数调用期间它将为 null 。 然而,正如我所说,内存模型存在一些奇怪之处。
编辑:为了双重检查锁定的目的,如果您的字段是
易失性
并且如果您使用Java,则您可以摆脱这个 5或更高。 在 Java 5 之前,内存模型对此还不够强大。 不过,您需要完全正确地获得模式。 有关更多详细信息,请参阅《Effective Java》,第 2 版,第 71 项。编辑:这是我反对亚伦的内联在单个线程中可见的理由。 假设我们有:
我相信这将总是报告
true
。 来自第 15.26.1 节:然后从 第 17.4.5 节:
两个操作可以按先行发生关系排序。 如果一个操作发生在另一个操作之前,则第一个操作对第二个操作可见并且排序在第二个操作之前。
换句话说,即使在单个线程内发生奇怪的事情也是可以的,但一定不能被观察到。 在这种情况下,差异将是可以观察到的,这就是为什么我认为这是非法的。
If another thread were to check the
someObject
variable "during" construction, I believe it may (due to quirks in the memory model) see a partially initialized object. The new (as of Java 5) memory model means that any final fields should be set to their values before the object becomes visible to other threads (so long as the reference to the newly created object doesn't escape from the constructor in any other way) but beyond that there aren't many guarantees.Basically, don't share data without appropriate locking (or guarantees given by static inializers etc) :) Seriously, memory models are seriously tricky, as is lock-free programming in general. Try to avoid this becoming a possibility.
In logical terms the assignment happens after the constructor runs - so if you observe the variable from the same thread it will be null during the constructor call. However, as I say there are memory model oddities.
EDIT: For the purposes of double-checked locking, you can get away with this if your field is
volatile
and if you're using Java 5 or higher. Prior to Java 5 the memory model wasn't strong enough for this. You need to get the pattern exactly right though. See Effective Java, 2nd edition, item 71 for more details.EDIT: Here's my reasoning for arguing against Aaron's inlining being visible in a single thread. Suppose we have:
I believe this will always report
true
. From section 15.26.1:Then from section 17.4.5:
Two actions can be ordered by a happens-before relationship. If one action happens-before another, then the first is visible to and ordered before the second.
In other words, it's okay for weird stuff to happen even within a single thread but that mustn't be observable. In this case the difference would be observable, which is why I believe it would be illegal.
someObject
将在构造过程中的某个时刻变为非null
。 通常有两种情况:在第一种情况下,VM 将执行以下代码(伪代码):
因此在这种情况下,
someObject
不是null
并且 它指向内存这不是 100% 初始化,即并非所有构造函数代码都已运行! 这就是为什么双重检查锁定不起作用的原因。在第二种情况下,构造函数中的代码将运行,引用将被传回(就像在普通方法调用中一样),并且 someObject 将被设置为引用的值之后所有和每个初始化代码已运行。
问题是没有办法告诉java不要提前分配
someObject
。 例如,您可以尝试:但由于未使用 tmp,优化器可以忽略它,因此它将生成与上面相同的代码。
因此,这种行为是为了让优化器生成更快的代码,但在编写多线程代码时它可能会给你带来麻烦。 在单线程代码中,这通常不是问题,因为在构造函数完成之前不会执行任何代码。
[编辑] 这是一篇很好的文章,解释了正在发生的事情: http:// /www.ibm.com/developerworks/java/library/j-dcl.html
PS:书“Effective Java,第二版" 包含适用于 Java 5 及更高版本的解决方案:
看起来很奇怪,但应该适用于每个 Java VM。 请注意,每一点都很重要; 如果省略双重分配,您将获得糟糕的性能或部分初始化的对象。 要获得完整的解释,请购买这本书。
someObject
will become non-null
at some point during the construction. Typically, there are two cases:In the first case, the VM will execute this code (pseudocode):
So in this case,
someObject
is notnull
and it points to memory that is not 100% initialized, namely not all of the constructor code has been run! This is why double-checked locking doesn't work.In the second case, the code from the constructor will run, a reference will be passed back (just like in a normal method call) and someObject will be set to the value of the refernce after all and every init code has run.
The problem is that there is no way to tell java not to assign
someObject
early. For example, you could try:But since tmp is not used, the optimizer is allowed to ignore it, so it would produce the same code as above.
So this behavior is to allow the optimizer to produce faster code but it can bite you nastily when writing multi-threaded code. In single threaded code, this is usually not an issue since no code is executed until the constructor finishes.
[EDIT] Here is a good article which explains what's happening: http://www.ibm.com/developerworks/java/library/j-dcl.html
PS: The book "Effective Java, Second Edition" by Joshua Bloch contains a solution for Java 5 and up:
Looks weird but should work on every Java VM. Note that every bit is important; if you omit the double assign, you'll either get bad performance or partially initialized objects. For a complete explanation, buy the book.
someObject
将是一个空指针,直到从该类型的构造函数为它分配一个指针值为止。 由于赋值是从右到左的,因此另一个线程可能在构造函数仍在运行时检查someObject
。 这将发生在将指针分配给变量之前,因此someObject
仍将为 null。someObject
will be a null pointer right up until it is assigned a pointer value from the constructor of the type. Since assignment is from right to left it is possible for another thread to checksomeObject
while the constructor is still running. This would be prior to the assignment of the pointer to the variable sosomeObject
would still be null.从另一个线程来看,您的对象仍然看起来为空,直到构造函数完成执行。 这就是为什么如果构造因异常而终止,引用将保持为空。
where
确保在另一个对象上同步,而不是在正在构造的对象上同步。
From another thread, your object will still look null until the constructor has finished executing. This is why if the construction is terminated by an exception, the reference will remain null.
where
Make sure to synchronize on another object, not the one being constructed.
对于第一个示例:构造函数完成后,someObject 变为非空。 如果您从另一个线程进行检查,构造函数完成后 someObject 将变为非空。 请注意,您永远不应该从不同的线程访问不同步的对象,因此您的示例不应该在实际代码中以这种方式实现。
对于第二个示例, someObject 永远不会为 null,因为它是在构建 SomeClass 本身并使用新创建的对象创建并初始化 someObject 之后构建的。 对于线程来说也是如此:不要在没有同步的情况下从不同线程访问此变量!
For your first example: someObject becomes non-null AFTER the constructor has completed. If you would check from another thread, someObject would become non-null after the constructor has finished. Beware, you should never access unsynchronized objects from different threads, so your example should not be implemented that way in real-world code.
For the second example, someObject would never be null as it is constructed AFTER SomeClass itself is constructed and someObject is created&initialized with the newly created object. Same here for threads: don't access this variable from different threads without synchronisation!
下面是一些测试代码,显示在构造函数完成运行之前该对象为 null:
输出:
Here's some test code which shows that the object is null until the constructor has finished running:
Output: