Singleton Pattern in Java: Best Practices

March 11, 2014

Creational patterns such as Singleton provide mechanisms for object instantiation. The purpose of the Singleton is to ensure that only one instance of a particular class is created and provide global access to it.

There are many opinionated views about the approaches of  implementing Singleton among developers in terms of efficiency and java memory model. The way I see it there are 4 main approaches of implementing this pattern in java which are both efficient and thread-safe.

1. Early/eager initialization with a static factory method
2. Lazy initialization with double-checked locking
3. Initialization on demand holder
4. Enum singleton

Early/eager initialization with a static factory method

This is the simplest way of creating a Singleton class and has its own benefits and drawbacks. Here because of the static initialization, the Singleton instance is created when the class is loaded to the JVM. So there is absolutely no need of explicit synchronization, hence there will be no thread safety issues, but this is not the best approach if creating the object is costly in terms of resources/time. If we can't guarantee that this instance will be used by the client, this approach will be a waste of resources. So early/eager initialization is suitable when the instance is used frequently and creating the instance is less expensive.
Static block initialization is a slight variation of the above approach. This method can be used to check when there is a risk of throwing an Exception when the instance is created which is also the solution to avoid creating instances using Reflection.

Lazy initialization with double-checked locking

This approach led to some controversy regarding the java memory model and thread safety. But after the introduction of volatile keyword in Java 5 those subtle problems have been fixed.
Lazy initialization ensures that the Singleton object is initialized only after the getInstance() method is called hence this is better solution than early initialization when it comes to expensive object creation. Double-checked locking mechanism is used to ensure that only a single thread initializes the object by acquiring a lock on it.
Prior to Java 5 without the volatile keyword, when one thread has partially initialized the object, another thread will notice that instance is already initialized. What volatile does is it notifies other threads when the object is still being created.

Initialization on demand holder

First introduced by Bill Pugh, this approach is one of my favorites which requires no synchronization and works with all Java versions. Using the concept that object is initialized when the class is loaded by the ClassLoader, it's more like an extended version of early initialization approach except using a static inner class as a holder.

Enum Singleton

Item 3, Effective Java 2nd edition by Joshua Bloch suggests that this approach is the best way to implement a Singleton. Enum Singleton is functionally equivalent to a Singleton created by early initialization except with more benefits such as not allowing deserialization to create new instances by default and the guarantee against multiple instantiation.


Further more on Implementing Singleton-Preventing Multiple Instantiation

Even with the private constructors and fields there are still ways of creating multiple instances such as Reflection, Cloning and Serialization. When this happens Singleton nature is broken and the Singleton will not be Singleton anymore. Let's look at some solutions for this.

1. I've already mentioned the solution for preventing instantiation using Reflection in the Early/eager initialization example in which we throw an Exception from the constructor.

2. As for cloning we can implement Cloneable interface and override clone() method to throw an Exception whenever attempted to clone the class or instead we can return the Singleton instance also.

3. The problem with Serialization is when a serialized object is deserialized It returns a new instance. To overcome this we have to implement one of the so called "magic methods". ie. readResolve(), which does not belong to any class or interface but related to Serializable interface. 
Enum Singleton approach can be used as a solution too.
Thank you for reading.


No comments:

Post a Comment