Does object construction guarantee in practice that all threads see non-final fields initialized?
But anecdotal evidence suggests that it doesn't happen in practice
To see this issue, you have to avoid using any memory barriers. e.g. if you use thread safe collection of any kind or some System.out.println
can prevent the problem occurring.
I have seen a problem with this before though a simple test I just wrote for Java 8 update 161 on x64 didn't show this problem.
It seems there is no synchronization during object construction.
The JLS doesn't permit it, nor was I able to produce any signs of it in code. However, it's possible to produce an opposition.
Running the following code:
public class Main {
public static void main(String[] args) throws Exception {
new Thread(() -> {
while(true) {
new Demo(1, 2);
}
}).start();
}
}
class Demo {
int d1, d2;
Demo(int d1, int d2) {
this.d1 = d1;
new Thread(() -> System.out.println(Demo.this.d1+" "+Demo.this.d2)).start();
try {
Thread.sleep(500);
} catch(InterruptedException e) {
e.printStackTrace();
}
this.d2 = d2;
}
}
The output would continuously show 1 0
, proving that the created thread was able to access data of a partially created object.
However, if we synchronized this:
Demo(int d1, int d2) {
synchronized(Demo.class) {
this.d1 = d1;
new Thread(() -> {
synchronized(Demo.class) {
System.out.println(Demo.this.d1+" "+Demo.this.d2);
}
}).start();
try {
Thread.sleep(500);
} catch(InterruptedException e) {
e.printStackTrace();
}
this.d2 = d2;
}
}
The output is 1 2
, showing that the newly created thread will in fact wait for a lock, opposed to the unsynchronized exampled.
Related: Why can't constructors be synchronized?
Taking your example as the question itself - the answer would be yes, that is entirely possible. The initialized fields are visible only to the constructing thread, like you quoted. This is called safe publication (but I bet you already knew about this).
The fact that you are not seeing that via experimentation is that AFAIK on x86 (being a strong memory model), stores are not re-ordered anyway, so unless JIT would re-ordered those stores that T1 did - you can't see that. But that is playing with fire, literately, this question and the follow-up (it's close to the same) here of a guy that (not sure if true) lost 12 milion of equipment
The JLS guarantees only a few ways to achieve the visibility. And it's not the other way around btw, the JLS will not say when this would break, it will say when it will work.
1) final field semantics
Notice how the example shows that each field has to be final
- even if under the current implementation a single one would suffice, and there are two memory barriers inserted (when final(s) are used) after the constructor: LoadStore
and StoreStore
.
2) volatile fields (and implicitly AtomicXXX
); I think this one does not need any explanations and it seems you quoted this.
3) Static initializers well, kind of should be obvious IMO
4) Some locking involved - this should be obvious too, happens-before rule...