Thread B is blocked on the synchronize, when it proceeds it will see the changed field _instance != null
and therefore does not construct one but return the existing.
All other threads which come later see the instance being set and will not even lock.
Problem: your code is incomplete, you need volatile in order to make sure threads which do not go through the synchronized (most of them, hopefully) still only see a completely published singleton object.
The Java Memory Model does only guarantee that the final fields are initialized. For all others you need a safe publish, which is possible with:
- Exchange the reference through a properly locked field (JLS 17.4.5)
- Use static initializer to do the initializing stores (JLS 12.4)
- Exchange the reference via a volatile field (JLS 17.4.5), or as the consequence of this rule, via the AtomicX classes
- Initialize the value into a final field (JLS 17.5).
The easiest method to avoid the volatile (or an atomic reference which is also safe to publish objects to other threads) is to use normal Object initialisation, this is a valid and robust singleton (but not lazy) provided by the JVM:
class Singleton
{
private static final Singleton HIGHLANDER = new Singleton();
private Singleton() { } // not accessible
public static getSingleton() { return HIGHLANDER; }
}
JDK internally uses this similar construct with "Holder" objects to implement the same simple and robust pattern but in a lazy fashion:
class Singleton
{
private Singleton() { } // not accessible
private static Class LazyHolder {
private static final Singleton LAZY_HIGHLANDER = new Singleton();
}
public static Singleton getInstance() {
return LazyHolder.LAZY_HIGHLANDER;
}
}
Both methods do not require volatile
variable access (which you need in DCL case) or synchronisation (it is implicitly done by the JVM which does the initialisation protected by a class lock).