The Java memory model guarantees a happens-before relationship between an object's construction and finalizer:
There is a happens-before edge from the end of a constructor of an object to the start of a finalizer (§12.6) for that object.
As well as the constructor and the initialization of final fields:
An object is considered to be completely initialized when its constructor finishes. A thread that can only see a reference to an object after that object has been completely initialized is guaranteed to see the correctly initialized values for that object's final fields.
There's also a guarantee about volatile
fields since, there's a happens-before relations with regard to all access to such fields:
A write to a volatile field (§8.3.1.4) happens-before every subsequent read of that field.
But what about regular, good old non-volatile fields? I've seen a lot of multi-threaded code that doesn't bother creating any sort of memory barrier after object construction with non-volatile fields. But I've never seen or heard of any issues because of it and I wasn't able to recreate such partial construction myself.
Do modern JVMs just put memory barriers after construction? Avoid reordering around construction? Or was I just lucky? If it's the latter, is it possible to write code that reproduces partial construction at will?
Edit:
To clarify, I'm talking about the following situation. Say we have a class:
public class Foo{
public int bar = 0;
public Foo(){
this.bar = 5;
}
...
}
And some Thread T1
instantiates a new Foo
instance:
Foo myFoo = new Foo();
Then passes the instance to some other thread, which we'll call T2
:
Thread t = new Thread(() -> {
if (myFoo.bar == 5){
....
}
});
t.start();
T1
performed two writes that are interesting to us:
- T1 wrote the value 5 to
bar
of the newly instantiatedmyFoo
- T1 wrote the reference to the newly created object to the
myFoo
variable
For T1, we get a guarantee that write #1 happened-before write #2:
Each action in a thread happens-before every action in that thread that comes later in the program's order.
But as far as T2
is concerned the Java memory model offers no such guarantee. Nothing prevents it from seeing the writes in the opposite order. So it could see a fully built Foo
object, but with the bar
field equal to equal to 0.
Edit2:
I took a second look at the example above a few months after writing it. And that code is actually guaranteed to work correctly since T2
was started after T1
's writes. That makes it an incorrect example for the question I wanted to ask. The fix it to assume that T2 is already running when T1
is performing the write. Say T2
is reading myFoo
in a loop, like so:
Foo myFoo = null;
Thread t2 = new Thread(() -> {
for (;;) {
if (myFoo != null && myFoo.bar == 5){
...
}
...
}
});
t2.start();
myFoo = new Foo(); //The creation of Foo happens after t2 is already running