Implementing the Singleton Design Pattern
How will you ensure that a class has only one instance? There are several ways of doing it in Java. Following are the ways:
- Declare a private constructor to prevent others from making object of class.
- Create the object of the class either during class loading in a static block, or in a static method that first checks whether the object exists or not and creates a new object only if it doesnβt exist.
Letβs see all the possible solutions with code samples one by one:
1. Eagerly Initialized Singleton
This is the simplest approach in which object is created at the time of class loading -
public class EagerSingleton {
/** private constructor to prevent others from instantiating this class */
private EagerSingleton() {}
/** Create an instance of the class at the time of class loading */
private static final EagerSingleton instance = new EagerSingleton();
/** Provide a global point of access to the instance */
public static EagerSingleton getInstance() {
return instance;
}
}
The disadvantage of this approach is that the instance is created irrespective of whether it is used or not. It can have performance implication as object will be created without its use.
2. Eagerly Initialized Static Block Singleton
We can also create the one instance of the class in a static block. It works because the static block is executed only once at the time of class loading.
The benefit with static block initialization is that you can write your initialization logic or handle exceptions in the static block.
public class EagerStaticBlockSingleton {
private static final EagerStaticBlockSingleton instance;
/** Don't let anyone else instantiate this class */
private EagerStaticBlockSingleton() {}
/** Create the one-and-only instance in a static block */
static {
try {
instance = new EagerStaticBlockSingleton();
} catch (Exception ex) {
throw ex;
}
}
/** Provide a public method to get the instance that we created */
public static EagerStaticBlockSingleton getInstance() {
return instance;
}
}
Like the previous solution, the instance is created whether or not it is needed by the application.
3. Lazily Initialized Singleton
Lazy initialization means making object only when it is needed.
In the following example, we first check whether the object is already created or not in the getInstance()
method. If the object is already created, we simply return it, otherwise, we first create the object and then return it:
public class LazySingleton {
private static LazySingleton instance;
/** Don't let anyone else instantiate this class */
private LazySingleton() {}
/** Lazily create the instance when it is accessed for the first time */
public static synchronized LazySingleton getInstance() {
if(instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
Focus on the use of synchronized
keyword in the getInstance()
method. This is required to prevent race conditions in multi-threaded environments.
Letβs say that the object
is not created yet, and two threads enter the getInstance()
method simultaneously. In that case, the instance==null
check will be equal to true and both the threads will create a new object of the class.
The synchronized
keyword ensures that only one thread can execute the getInstance()
method at one time.
4. Lazily Initialized Double-Checked Locking Singleton
The synchronized
keyword in the getInstance()
method prevents race conditions, but it also has some performance issue
Below is an optimized way of the lazily initialized singleton where instead of making the entire method synchronized
, we create a synchronized
block and write only the instantiation part in the synchronized
block -
public class LazyDoubleCheckedLockingSingleton {
private static volatile LazyDoubleCheckedLockingSingleton instance;
/** private constructor to prevent others from instantiating this class */
private LazyDoubleCheckedLockingSingleton() {}
/** Lazily initialize the singleton in a synchronized block */
public static LazyDoubleCheckedLockingSingleton getInstance() {
if(instance == null) {
synchronized (LazyDoubleCheckedLockingSingleton.class) {
// double-check
if(instance == null) {
instance = new LazyDoubleCheckedLockingSingleton();
}
}
}
return instance;
}
}
The above approach is called Double-Checked Locking because we double-check whether the variable is initialized or not inside the synchronized
block.
The double-checking is very important here. Letβs take that two threads T1
and T2
enter the getInstance()
method at the same time. The instance==null
check will be equal to true, so both of them will enter into the synchronized
block one-by-one. If the double check was not there, both threads would have created a new instance.
Also, focus on the use of volatile
keyword with the instance variable. This is important to prevent compilers from doing their own optimizations and handle the singleton correctly.
5. Lazily Initialized Inner Class Singleton (Bill Pugh singleton)
Bill Pugh came up with a highly efficient solution to create singleton. It is called Initialization-on-demand holder idiom. In this , a static inner class is used to lazily create a singleton object.
public class LazyInnerClassSingleton {
/** private constructor to prevent others from instantiating this class */
private LazyInnerClassSingleton() {}
/** This inner class is loaded only after getInstance() is called for the first time. */
private static class SingletonHelper {
private static final LazyInnerClassSingleton INSTANCE = new LazyInnerClassSingleton();
}
public static LazyInnerClassSingleton getInstance() {
return SingletonHelper.INSTANCE;
}
}
Note that, the inner class is not loaded unless the getInstance()
method is invoked for the first time. This way of solution is thread-safe and doesnβt require any synchronization. It is most efficient solution among all the singleton design pattern implementations.
6. Enum Singleton
An Enum is singleton by design. All the enum values are initialized only once during class loading.
import java.util.Arrays;
/** An Enum value is initialized only once at the time of class loading.
It is singleton by design and is also thread-safe.
*/
enum EnumSingleton {
WEEKDAY("Monday", "Tuesday", "Wednesday", "Thursday", "Friday"),
WEEKEND("Saturday", "Sunday");
private String[] days;
EnumSingleton(String ...days) {
System.out.println("Initializing enum with " + Arrays.toString(days));
this.days = days;
}
public String[] getDays() {
return this.days;
}
@Override
public String toString() {
return "EnumSingleton{" +
"days=" + Arrays.toString(days) +
'}';
}
}
public class EnumSingletonExample {
public static void main(String[] args) {
System.out.println(EnumSingleton.WEEKDAY);
System.out.println(EnumSingleton.WEEKEND);
}
}
# Output
Initializing enum with [Monday, Tuesday, Wednesday, Thursday, Friday]
Initializing enum with [Saturday, Sunday]
EnumSingleton{days=[Monday, Tuesday, Wednesday, Thursday, Friday]}
EnumSingleton{days=[Saturday, Sunday]}