Singleton Design Pattern
It’s a Creational Design Pattern which talks about maintaining a single instance of a class throughout the application lifetime i.e., from start till the end of runtime process.
Purpose: When we want to have a globally accessible object that is created only once within the application. Examples: Application Logging, Application Configuration Settings, in memory cache.
This post will have code Snippets in Java.
There are multiple ways to implement Singleton Pattern, I will try to depict some popular variants.
-
Eagerly initialized Singleton
public class EagerSingleton { private static final EagerSingleton INSTANCE = new EagerSingleton(); private EagerSingleton(){} public static EagerSingleton getInstance() { return INSTANCE; } } -
Lazily initialized Singleton
public class LazySingleton { private static LazySingleton instance; private LazySingleton() {} public static LazySingleton getInstance() { if(instance == null) { instance = new LazySingleton(); } return instance; } }- Problem: In concurrent environments it will fail miserably by creating more than one object and the code is not thread-safe.
- Solution: Locking the critical section is vital in multithreaded environments, and in programming languages like
Javathere is more than one way to do this locking, but will focus on how it can be achieved throughsynchronizedkeyword.
-
Lazily initialized Singleton - Improved to work in multithreaded environments
public class LazySingleton { /*- volatile is required here to save the instance in main memory which ensures that threads operating upon this object gets the latest value. Without volatile each thread maintains local copy of static variable, and each thread is unaware if the instance variable is already initialized */ private static volatile LazySingleton instance; private LazySingleton() {} public synchronized static LazySingleton getInstance() { if(instance == null) { instance = new LazySingleton(); } return instance; } }- Problem: Applying
synchronizedat method level does lock above and beyond the critical section resulting in slightly less performant code. - Solution: Applying
synchronizedat block level instead of method level would bring some improvements to performance, by locking only the critical section.
- Problem: Applying
-
Lazily initialized Singleton - Double-Checked Locking - Performance improvement
public class LazySingleton { /*- volatile is required here to save the instance in main memory which ensures that threads operating upon this object gets the latest value. Without volatile each thread maintains local copy of static variable, and each thread is unaware if the instance variable is already initialized */ private static volatile LazySingleton instance; private LazySingleton() {} public static LazySingleton getInstance() { if(instance == null) { synchronized(LazySingleton.class) { // Class Level Lock if(instance == null) { // Double-Check instance = new LazySingleton(); } } } return instance; } } -
Alternatively, there is neat approach to implement Singleton without using
synchronizedand yet achieve thread-safety. It’s known as Initialization on demandpublic class LazySingleton { private LazySingleton() {} private static class LazySingletonHolder { private static final LazySingleton INSTANCE = new LazySingleton(); } public static LazySingleton getInstance() { return LazySingletonHolder.INSTANCE; } } - Common Problems in all the above Singleton implementations:
- Pattern can be broken by creating multiple objects through Reflection
- And, there are other petty complaints
- It can be cloneable. Yes it is possible only when we implement
Cloneableinterface and overridejava.lang.Object’sclone()method. But why would anyone want to implement Cloneable interface in a Singleton? - It has serialization issues. Yes it has this problem exposed when we implement
Serializableinterface, and it creates a new object during deserialization process.- Below is the excerpt from my edited StackOverflow Answer
During deserialization process we call
readObject()which is an existing method inObjectInputStreamclass. At the time of deserializationreadObject()method internally checks whether the object that is being deserialized hasreadResolve()method implemented. IfreadResolve()method exists then it will be invoked. A samplereadResolve()implementation would look like this.protected Object readResolve() { return INSTANCE; }So, the intent of writing
readResolve()method is to ensure that the same object that lives in JVM is returned instead of creating new object during deserialization.
- Below is the excerpt from my edited StackOverflow Answer
- But, the real question is why do you want to serialize a Singleton?
- It can be cloneable. Yes it is possible only when we implement
-
Finally, the recommended approach for implementing Singleton in Java is using
enumpublic enum UserInfo { SRK("SaiRaghava K", 99); private String name; private int age; UserInfo(String name, int age) { this.name = name; this.age = age; } public int getAge() { return age; } public String getName() { return name; } }- An
enumimplicitly implementsSerializable, and it ensures that same object is returned during deserialization as well. Unlike a Singleton that’s implemented usingclassconstruct, inenumbased singletons we don’t have to addreadResolve()method which guarantees that only a single instance is maintained. -
Singleton pattern implemented using
classcan be broken using reflection, however this is restricted forenumbased Singleton implementation. If any such attempt is made, it might result in exception Stacktrace as shown below.Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
- An
- In sum, it’s recommended to implement Singleton pattern using
enumin Java, and it’s crucial to keep the Singleton state immutable.
References: