javamultithreadingjvmjava-memory-modelsafe-publication

Is it possible to guarantee safe publication of a non-final field in Java?


Let's imagine we have some class with one volatile non-final field that we want to initialise with a default value passed through a constructor:

public class MyClass {
    private volatile String s;

    MyClass(String s) {
        this.s = s;
    }
}

We want to use class MyClass in a concurrent environment and we want to have guarantees that if some thread gets a reference to some instance of MyClass, then s is properly initialised and visible for the thread.

If the field s is final, it would be safely published due to the final semantics guarantees. This is well documented in JMM (https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html) and doesn't make any questions. However, in our example, s is not final and the final semantics doesn't apply here.

We could think that as long as a is volatile, there's a happens-before relation between a write in constructor and a read in some other thread. However, OpenJDK has a test that proves that there's no such guarantee here: https://github.com/openjdk/jcstress/blob/0.16/jcstress-samples/src/main/java/org/openjdk/jcstress/samples/jmm/advanced/AdvancedJMM_13_VolatileVsFinal.java.

Given all these considerations, how fare it is to say that it's impossible to guarantee a safe publication of any mutable non-final field? As long as we don't have a final modifier on our field, we can end up in the situation when some thread has a reference to an object whose fields, even if they're probably volatile, may not be properly initialised, and this problem is hard to fix.

The only solution I see here is to make a thread, which creates an instance of MyClass, to pass a reference to the instance though a volatile reference. Then the other thread read will be related to the first thread write by the happens-before relation, and the second thead will be forced to see all the changes made by the first thread in the constructor, including the write to the volatile variable.


Solution

  • The spec writes:

    A write to a volatile variable v synchronizes-with all subsequent reads of v by any thread (where "subsequent" is defined according to the synchronization order).

    Note the "subsequent" part. Making the field volatile does not ensure that all reads of the field are subsequent to the write, it only ensures that if a read is subsequent, it will see the write.

    That is, reading a volatile field will not wait for the field to have been written, but read it right away. If you wish to ensure that the field has been written at the time it is read, you need to employ some means of waiting to delay that read. More formally, the thread that initializes the object must, after initialization is done, synchronize-with the thread that wishes to read the object, thereby establishing a happens-before relationship between the write of the field, and the read (in fact, if you do that, the field itself needn't be volatile).

    You can use any of the synchronization actions the Java Language provides to effect that synchronization.

    Put diffently, if a thread safely publishes the object after it has been initialized, the field doesn't need to be volatile. But if the thread shares a reference to the object under construction through a data race, making its field volatile will not ensure it has been written before it is read.