Correct Singleton in Java

    I am sure that each reader knows what the “Singleton” design pattern is, but not everyone knows how to program it efficiently and correctly. This article is an attempt to aggregate existing knowledge on this issue.

    In addition, you can consider the article as a continuation of the remarkable study published on Habrahabr earlier.

    Lazy Singleton in Java

    The author knows two ways to implement a template with normal initialization.

    1 Static field

    public class Singleton {
    	public static final Singleton INSTANCE = new Singleton();
    }

    + Simple and transparent implementation
    + Thread safety
    - No lazy initialization

    2 Enum Singleton

    According to Joshua Bloch, this is the best way to implement the template [1].

    public enum Singleton {
    	INSTANCE;
    }
    

    + Witty
    + Serialization out of the box
    + Thread safety out of the box
    + Ability to use EnumSet, EnumMap, etc.
    + Switch support
    - No lazy initialization

    Lazy Singleton in Java

    At the time of writing, there are at least three valid implementations of the Singleton template with lazy initialization in Java.

    1 Synchronized Accessor

    public class Singleton {
    	private static Singleton instance;
    	public static synchronized Singleton getInstance() {
    		if (instance == null) {
    			instance = new Singleton();
    		}
    		return instance;
    	}
    }
    

    + Lazy initialization
    - Poor performance (critical section) in the most typical access

    2 Double Checked Locking & volatile

    public class Singleton {
            private static volatile Singleton instance;
            public static Singleton getInstance() {
    		Singleton localInstance = instance;
    		if (localInstance == null) {
    			synchronized (Singleton.class) {
    				localInstance = instance;
    				if (localInstance == null) {
    					instance = localInstance = new Singleton();
    				}
    			}
    		}
    		return localInstance;
    	}
    }
    

    + Lazy initialization
    + High performance
    - Only supported with JDK 1.5 [5]

    2.1 Why doesn’t it work without volatile?

    The problem of the Double Checked Lock idiom lies in the Java memory model, more precisely in the order in which objects are created. You can conditionally imagine this order in the following steps [2, 3]:

    Let us create a new student: Student s = new Student (), then

    1) local_ptr = malloc (sizeof (Student)) // allocate memory for the object itself;
    2) s = local_ptr // initialization of the pointer;
    3) Student :: ctor (s); // object construction (field initialization);

    Thus, between the second and third stages, a situation is possible in which another thread can receive and start using (based on the condition that the pointer is not zero) an incompletely constructed object. Actually, this problem was partially solved in JDK 1.5 [5], however, the authors of JSR-133 [5] recommend using voloatile for Double Chess Locked. Moreover, their attitude to such things can be easily traced from the commentary on the specification:

    There exist a number of common but dubious coding idioms, such as the double-checked locking idiom, that are proposed to allow threads to communicate without synchronization. Almost all such idioms are invalid under the existing semantics, and are expected to remain invalid under the proposed semantics.

    Thus, although the problem has been resolved, using Double Checked Lock without volatile is extremely dangerous. In some cases, depending on the implementation of the JVM, operating environment, scheduler, etc., this approach may not work. However, a series of experiments accompanied by viewing the assembler code generated by JIT by the author failed to raise such a case.

    Finally, Double Checked Lock can be used without exception with immutable objects (String, Integer, Float, etc.).

    3 On Demand Holder idiom

    public class Singleton {
    	public static class SingletonHolder {
    		public static final Singleton HOLDER_INSTANCE = new Singleton();
    	}
    	public static Singleton getInstance() {
    		return SingletonHolder.HOLDER_INSTANCE;
    	}
    }
    


    + Lazy initialization
    + High performance
    - Cannot be used for non-static class fields

    Performance

    To compare the performance of the above methods, a micro-benchmark was used [6], which determines the number of elementary operations (field increment) per second over a Singleton object, from two parallel threads.

    The measurements were performed on a dual-core Intel Core 2 Duo T7300 2GHz machine, 2Gb ram and Java HotSpot (TM) Client VM (build 17.0-b17). The unit of speed is the number of increments (and hence the object’s captures) per second * 100,000.

    (More is better)
    ClientServer
    Synchronized accessor42.686.3
    Double Checked Lock & volatile179.8202.4
    On demand holder181.6202.7


    Conclusion: if you choose the right implementation of the template, you can get acceleration (speed up) from 2x to 4x .

    Summary

    We can distinguish the following short tips on using this or that approach to implement the “Loner” template [1].

    1) Use normal (not lazy) initialization wherever possible;
    2) For static fields use On Demand Holder idom;
    3) For simple fields use Double Chedked Lock & volatile idom;
    4) In all other cases, use the Syncronized accessor;

    Java Class Library & Singleton

    It is noteworthy that the developers of the Java Class Library have chosen the easiest way to implement the template - Syncronized Accessor. On the one hand, it is a guarantee of compatibility and proper operation. On the other hand, this is the loss of processor time for entering and exiting the critical section with each call.

    A quick grep search by source made it clear that there are a lot of such places in JCL.

    Perhaps the next article will be “What happens if all Singleton classes are written correctly in the Java Class Library?” :)

    Links
    [1] Joshua Bloch, Effective Java Reloaded talk at Google I / O 2008 ( video );
    [2] Double-checked locking and the Singleton pattern ;
    [3]The "Double-Checked Locking is Broken" Declaration ;
    [4] en.wikipedia.org/wiki/Double-checked_locking
    [5] JSR-133
    [6] How to write microbenchmarks

    Also popular now: