Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java Memory Model: Is it safe to create a cyclical reference graph of final instance fields, all assigned within the same thread?

Can somebody who understand the Java Memory Model better than me confirm my understanding that the following code is correctly synchronized?

class Foo {
    private final Bar bar;

    Foo() {
        this.bar = new Bar(this);
    }
}

class Bar {
    private final Foo foo;

    Bar(Foo foo) {
        this.foo = foo;
    }
}

I understand that this code is correct but I haven't worked through the whole happens-before math. I did find two informal quotations that suggest this is lawful, though I'm a bit wary of completely relying on them:

The usage model for final fields is a simple one: Set the final fields for an object in that object's constructor; and do not write a reference to the object being constructed in a place where another thread can see it before the object's constructor is finished. If this is followed, then when the object is seen by another thread, that thread will always see the correctly constructed version of that object's final fields. It will also see versions of any object or array referenced by those final fields that are at least as up-to-date as the final fields are. [The Java® Language Specification: Java SE 7 Edition, section 17.5]

Another reference:

What does it mean for an object to be properly constructed? It simply means that no reference to the object being constructed is allowed to "escape" during construction. (See Safe Construction Techniques for examples.) In other words, do not place a reference to the object being constructed anywhere where another thread might be able to see it; do not assign it to a static field, do not register it as a listener with any other object, and so on. These tasks should be done after the constructor completes, not in the constructor. [JSR 133 (Java Memory Model) FAQ, "How do final fields work under the new JMM?"]

like image 467
Luis Casillas Avatar asked Mar 26 '15 04:03

Luis Casillas


People also ask

Why are final variables thread safe in Java?

When variables are shared by multiple threads, the atomic variable ensures that threads don’t crash into each other. Final Variables are also thread-safe in java because once assigned some reference of an object It cannot point to reference of another object. Writing code in comment?

Why is the JVM not reclaiming all the memory associated with connection?

The resulting behavior is that while the JVM will reclaim memory due to unreachable objects being collected, resources (including memory) associated with the Connection object might not be reclaimed. As such, Connection's finalize method does not clean up everything.

How does the JVM ensure the second thread sees the final field?

The JVM ensures that the second thread sees the state of the final field corresponding to the freeze action — no more, no less. First, let's agree on how I described steps of the execution: Variable temp is the given object that we want to publish. Using nfv = temp , we expose the publication of a reference of our object.

Why does the JVM only initialize one class at a time?

The reason why this is safe is that because the JVM always performs object initialization on a single thread and ensures that the initialization happens before everything that uses that class (no other synchronization needed)


1 Answers

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:

  1. 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.)

  2. 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.

  3. 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.)

like image 97
Rafael Winterhalter Avatar answered Oct 12 '22 23:10

Rafael Winterhalter