Search code examples
javaconcurrencyvolatilejava-memory-modelsafe-publication

Use volatile field to publish an object safely


From the book Java Concurrency In Practice:

To publish an object safely, both the reference to the object and the object’s state must be made visible to other threads at the same time. A properly constructed object can be safely published by:

  • Initializing an object reference from a static initializer;
  • Storing a reference to it into a volatile field or AtomicReference;
  • Storing a reference to it into a final field of a properly constructed object; or
  • Storing a reference to it into a field that is properly guarded by a lock.

My question is:

Why does the bullet point 3 have the constrain:"of a properly constructed object", but the bullet point 2 does not have?

Does the following code safely publish the map instance? I think the code meets the conditions of bullet point 2.

public class SafePublish {

    volatile DummyMap map = new DummyMap();

    SafePublish() throws InterruptedException {
        new Thread(new Runnable() {
            @Override
            public void run() {
                // Safe to use 'map'?
                System.out.println(SafePublish.this.map);
            }
        }).start();
        Thread.sleep(5000);
    }

    public static void main(String[] args) throws InterruptedException {
        SafePublish safePublishInstance = new SafePublish();
    }
 

public class DummyMap {
    DummyMap() {
        System.out.println("DummyClass constructing");
      }
  }
} 

The following debug snapshot pic shows the map instance is null at the time of the execution is entering the construction of SafePublish. What happens if another thread now trying to read the map reference? Is it safe to read?

enter image description here


Solution

  • It's because final fields are guaranteed to be visible to other threads only after the object construction while the visibility of writes to volatile fields are guaranteed without any additional conditions.

    From jls-17, on 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.

    on volatile fields:

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

    Now, regarding your specific code example, JLS 12.5 guarantees that field initialization occurs before the code in your constructor is executed (see steps 4 and 5 in JLS 12.5, which is a bit too long to quote here). Therefore, Program Order guarantees that the constructor's code will see map initialized, regardless of whether it's volatile or final or just a regular field. And since there's a Happens-Before relation before field writes and the start of a thread, even the thread you're creating in the constructor will see map as initialized.

    Note that I specifically wrote "before code in your constructor is executed" and not "before the constructor is executed" because that's not the guarantee JSL 12.5 makes (read it!). That's why you're seeing null in the debugger before the first line of the constructor's code, yet the code in your constructor is guaranteed to see that field initialized.