Notice that we do several reads of instance in this code, and at least "read 1" and "read 3" are the reads without any synchronization — that is, those reads are racy. One of the intents of the Java Memory Model is to allow reorderings for ordinary reads, otherwise the performance costs would be prohibitive. Specification-wise, as mentioned in happens-before consistency rules, a read action can observe the unordered write via the race. This is decided for each read action, regardless what other actions have already read the same location. In our example, that means that even though "read 1" could read non-null instance , the code then moves on to returning it, then it does another racy read, and it can read a null instance , which would be returned!

One could think that after "check 1" had succeeded, the Singleton instance is properly initialized, and we can return it. That is not correct: the Singleton contents are only fully visible to the constructing thread! There are no guarantees that you will see Singleton contents properly in other threads, because you are racing against the initializer thread. Once again, even if you have observed the non-null instance , it does not mean you observe its internal state properly. In JMM-ese speak, there is no happens-before between the initializing stores in Singleton constructor, and the reads of Singleton fields.

Alas, this construction does not work properly for two reasons.

This observation gives rise to Double-Checked Locking idiom. It tries to evade synchronization if Singleton is already initialized, which is a common case after the one-time synchronization. Understanding that, most people go ahead and write:

…​as it has the properties (1), (2), and (3); but lacks property (4), since we do synchronization on every access for get() .

One can be reasonably sure this is not a good SingletonFactory :

It is efficient. The overheads for managing a Singleton state should be kept at minimum.

It is lazy. One can argue about this, but non-lazy factories are not interesting for our discussion. Singleton initialization should happen with the first request for a Singleton, not when Singleton class is initialized. If no one wants a Singleton instance, it should not be instantiated.

It is thread-safe. No matter how many threads are requesting a Singleton , all threads will get the same Singleton instance, regardless of the current state.

It provides the public API for getting a Singleton instance.

It is mildly irritating when people confuse Singletons , and Singleton Factories . For the sake of our discussion, we need to clearly separate these two notions. Singleton is an object that has only a single instance at every point of program life-cycle. Singleton Factory is an object that maintains Singletons . Of course, you can conflate these two in a single implementation, but that is not the point of this post.

Safe Publication

Now we are going to describe the concept of safe publication. Safe publication differs from a regular publication on one crucial point. Safe publication makes all the values written before the publication visible to all readers that observed the published object. It is a great simplification over the JMM rules of engagement with regards to actions, orders and such.

There are a few trivial ways to achieve safe publication:

Let us try to exploit each of those ways. The most trivial example is publishing through a properly locked field:

public class SynchronizedCLFactory { private Singleton instance ; public Singleton get () { synchronized ( this ) { if ( instance == null ) { instance = new Singleton (); } return instance ; } } }

This is the most trivial example spec-wise:

Mutual exclusion during get() calls allow only a single thread to do the Singleton initialization. The lock acquisition and releasing yield the synchronization actions that are bound in synchronizes-with, and then with happens-before. This forces the threads acquiring the lock to see the result of all the actions that were done before the lock release.

Classic holder idiom does roughly the same, but piggy-backs on class initialization locks. It safely publishes the object by doing the initialization in the static initializer. Note this thing is lazy, since we do not initialize Holder until the first call to get() :

public class HolderFactory { public static Singleton get () { return Holder . instance ; } private static class Holder { public static final Singleton instance = new Singleton (); } }

This is how it works spec-wise:

Class initialization is done under the lock, as per JLS 12.4.2. Class initialization lock provides the mutual exclusion during the class initialization, that is, only a single thread can initialize the static fields. The release of class initialization lock plays the necessary role in establishing the happens-before relationships between the actions in static initializers and any users of the static fields. Naively speaking, the propagation of memory effects requires any reader of static field to acquire the class initialization lock first, but JLS allows to elide that locking, if the memory effects are still maintained. Indeed, modern VMs do this optimization routinely.

Now, get back to the infamous volatile DCL, that works because now we safely publish the singleton via volatile :

public class SafeDCLFactory { private volatile Singleton instance ; public Singleton get () { if ( instance == null ) { // check 1 synchronized ( this ) { if ( instance == null ) { // check 2 instance = new Singleton (); } } } return instance ; } }

How can this concept be backed up by spec? Here’s how:

Volatile write and volatile reads of instance yield the actions bound in synchronizes-with order, and therefore form happens-before. That means the actions preceding the volatile store (that is, the actions in constructors) precede any actions after reading the instance . In other words, those threads that called get() will observe a fully-constructed Singleton . Volatile reads and writes of instance yield synchronization actions that are totally ordered, and consistent with program order. Therefore two consecutive reads of instance are guaranteed to see the same value, in the absence of intervening write to instance .

There is another way to provide the same guarantees: final fields. Since it is already too late to write to final field outside of constructor, we have to do a wrapper.

public class FinalWrapperFactory { private FinalWrapper wrapper ; public Singleton get () { FinalWrapper w = wrapper ; if ( w == null ) { // check 1 synchronized ( this ) { w = wrapper ; if ( w == null ) { // check2 w = new FinalWrapper ( new Singleton ()); wrapper = w ; } } } return w . instance ; } private static class FinalWrapper { public final Singleton instance ; public FinalWrapper ( Singleton instance ) { this . instance = instance ; } } }

How does it map to the actual spec?

The constructor writing out the final field has a freeze action at the end. You can read more on final fields semantics in "JMM Pragmatics". In short, if all the access chains contain the freeze action in-between the initializing stores to Singleton fields and the read of any Singleton field, we are allowed to observe only the initializing stores. Note this means if anyone had seen the Singleton instance before, then all bets are off: there is an access chain that bypasses the freeze action. In other words, it would be too late to "protect" already published object. Also notice we only do a single non-synchronized read of wrapper . Even though this read is racy, we recover from accidentally reading "null". If we were to read wrapper a second time before returning, that would set us up for an opportunity to read "null" again, and then return it.

For completeness, let us do a factory that still publishes via the race, but does only a single racy read, hopefully recovering after reading "null":

public class UnsafeLocalDCLFactory implements Factory { private Singleton instance ; // deliberately non-volatile @Override public Singleton getInstance () { Singleton res = instance ; if ( res == null ) { synchronized ( this ) { res = instance ; if ( res == null ) { res = new Singleton (); instance = res ; } } } return res ; } }

The introduction of local variable here is a correctness fix, but only partial: there still no happens-before between publishing the Singleton instance, and reading of any of its fields. We are only protecting ourselves from returning "null" instead of Singleton instance. The same trick can also be regarded as a performance optimization for SafeDCLFactory , i.e. doing only a single volatile read, yielding:

public class SafeLocalDCLFactory implements Factory { private volatile Singleton instance ; @Override public Singleton getInstance () { Singleton res = instance ; if ( res == null ) { synchronized ( this ) { res = instance ; if ( res == null ) { res = new Singleton (); instance = res ; } } } return res ; } }