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
Java
there is more than one way to do this locking, but will focus on how it can be achieved throughsynchronized
keyword.
-
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
synchronized
at method level does lock above and beyond the critical section resulting in slightly less performant code. - Solution: Applying
synchronized
at 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
synchronized
and 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
Cloneable
interface 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
Serializable
interface, 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 inObjectInputStream
class. 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
enum
public 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
enum
implicitly implementsSerializable
, and it ensures that same object is returned during deserialization as well. Unlike a Singleton that’s implemented usingclass
construct, inenum
based singletons we don’t have to addreadResolve()
method which guarantees that only a single instance is maintained. -
Singleton pattern implemented using
class
can be broken using reflection, however this is restricted forenum
based 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
enum
in Java, and it’s crucial to keep the Singleton state immutable.
References: