Uninitialized object leaked to another thread despite no code explicitly leaking it?

此生再无相见时 提交于 2019-11-27 09:20:24
Eugene

Here is what I think: because b is not final, the compiler is free to reorder the operations as it likes, right? So this, fundamentally is a reordering issue and as a result a unsafe publication issue Marking the variable as final will fix the problem.

More or less, it is the same example as provided here in the Java memory model docs.

The real question is how is this possible. I can also speculate here (since I have no idea how the compiler will reorder), but maybe the reference to B is written to the main memory (where it is visible to the other thread) BEFORE the write to x happens. In between these two operations the read happens, thus the zero value

Often the considerations surrounding concurrency focus on erroneous changes to state or on deadlocks. But visibility of state from different threads is equally important. There are many places in a modern computer where state can be cached. In registers, L1 cache on the processor, L2 cache between the processor and memory, etc. JIT compilers and the Java memory model are designed to take advantage of caching whenever possible or legal, because it can speed things up.

It can also give unexpected and counterintuitive results. I believe that is happening in this case.

When an instance of B is created, the instance variable x is briefly set to 0 before being set to whatever value was passed to the constructor. In this case, 1. If another thread tries to read the value of x, it could see the value 0 even if x has already been set to 1. It could be seeing a stale, cached value.

To ensure that the up-to-date value of x is seen, there are several things you can do. You could make x volatile, or you could protect the read of x with synchronization on the B instance (for example, by adding a synchronized getX() method). You could even change x from an int to a java.util.concurrent.atomic.AtomicInteger.

But by far the simplest way to correct the problem is to make x final. It is never going to change during the lifetime of B anyway. Java makes special guarantees for final fields, and one of them is that after the constructor completes, a final field set in the constructor will be visible to any other thread. That is, no other thread will see a stale value for that field.

Making fields immutable has many other benefits as well, but this is a great one.

See also Atomicity, Visibility, and Ordering by Jeremy Manson. Particularly the part where he says:

(Note: when I say synchronization in this post, I don't actually mean locking. I mean anything that guarantees visibility or ordering in Java. This can include final and volatile fields, as well as class initialization and thread starts and joins and all sorts of other good stuff.)

It seems to me that there might be a race condition on the B.x, such that there could exist a split second where B.x has been created and B.x=0 prior to the this.x = x in B's constructor. The series of events would be something like:

B is created (x defaults to 0) -> Constructor is ran -> this.x = x

Your thread is accessing B.x sometime after it was created but before the constructor ran. I was unable to recreate the problem locally, however.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!