Singleton Design Pattern is one of the basic and most useful Design Patterns in Java. It comes under the category of the creational pattern as defined in Gang of Four Design Patterns. The central idea behind this design pattern is to ensure that only a single instance of a class is created in the entire application scope.
It’s usage areas involve logging, caching, working with device driver objects etc. Frameworks like Spring make a heavy use of this pattern. java.lang.Runtime class is an example of a singleton class.
Any class is said to be implementing a Singleton design pattern if:
From the defined characteristics, it’s easy to infer that we can define a Java class to be singleton by:
So, the implementation would look almost like:
public class SingletonImpl { //instance member variable private static SingletonImpl obj; //private constructor private SingletonImpl(){ } //getter for retrieving instance public static SingletonImpl getInstance() { if(obj == null) { obj = new SingletonImpl(); } return obj; } }
Note that our getInstance() method instantiates an object only for the first time. For all other times, a reference to the same object is returned.
Also in our implementation above, we have done a lazy initialization. It simply means that an object creation will be postponed until we receive the first request to access it.
Our previous implementation will work fine for a single-threaded application.
But how will it behave in a multi-threaded environment? Is it thread-safe?
The answer is ‘No’. It is because multiple threads might still try to invoke getInstance() method at the same moment of time for the very first time. It might, therefore, result in the creation of multiple instances of our class.
One obvious solution is to ensure a synchronized access to our getInstance() method:
public class SingletonImpl { //instance member variable private static SingletonImpl obj; //private constructor private SingletonImpl(){ } //getter for retrieving instance public static synchronized SingletonImpl getInstance() { if(obj == null) { obj = new SingletonImpl(); } return obj; } }
However, this might result in poor performance as for all requests for accessing our object a lock has to be acquired each time.
A simple performance enhancement to our Singleton implementation would make use of a double-checking strategy:
public class SingletonImpl { //instance member variable private static SingletonImpl obj; //private constructor private SingletonImpl(){ } //getter for retrieving instance public static SingletonImpl getInstance() { if(obj == null) { synchronized(SingletonImpl.class) { if(obj == null) { obj = new SingletonImpl(); } } } return obj; } }
A small change of having a synchronized block results in high-performance gain as compared to our previous implementation. It is so because now an object lock will only be needed for the very first request of an instance. For all subsequent requests, the threads need not enter the synchronized context.
In this tutorial, we discussed the Singleton Design Pattern and looked at its Java implementation in both single-threaded and multi-threaded environment.