Can somebody who understand the Java Memory Model better than me confirm my understanding that the following code is correctly synchronized?
class Foo {
priv
Yes, it is safe. Your code does not introduce a data race. Hence, it is synchronized correctly. All objects of both classes will always be visible in their fully initialized state to any thread that is accessing the objects.
For your example, this is quite straight-forward to derive formally:
For the thread that is constructing the threads, all observed field values need to be consistent with program order. For this intra-thread consistency, when constructing Bar
, the handed Foo
value is observed correctly and never null
. (This might seem trivial but a memory model also regulates "single threaded" memory orderings.)
For any thread that is getting hold of a Foo
instance, its referenced Bar
value can only be read via the final
field. This introduces a dereference ordering between reading of the address of the Foo
object and the dereferencing of the object's field pointing to the Bar
instance.
If another thread is therefore capable of observing the Foo
instance altogether (in formal terms, there exists a memory chain), this thread is guaranteed to observe this Foo
fully constructed, meaning that its Bar
field contains a fully initialized value.
Note that it does not even matter that the Bar
instance's field is itself final
if the instance can only be read via Foo
. Adding the modifier does not hurt and better documents the intentions, so you should add it. But, memory-model-wise, you would be okay even without it.
Note that the JSR-133 cookbook that you quoted is only describing an implementation of the memory model rather than then memory model itself. In many points, it is too strict. One day, the OpenJDK might no longer align with this implementation and rather implement a less strict model that still fulfills the formal requirements. Never code against an implementation, always code against the specification! For example, do not rely on a memory barrier being placed after the constructor, which is how HotSpot more or less implements it. These things are not guaranteed to stay and might even differ for different hardware architectures.
The quoted rule that you should never let a this
reference escape from a constructor is also too narrow a view on the problem. You should not let it escape to another thread. If you would, for example, hand it to a virtually dispatched method, you could not longer control where the instance would end up. This is therefore a very bad practice! However, constructors are not dispatched virtually and you can safely create circular references in the manner you depicted. (I assume that you are in control of Bar
and its future changes. In a shared code base, you should document tightly that the constructor of Bar
must not let the reference slip out.)