One App, One Instance: The Singleton Pattern Made Easy✌️
Imagine you are in a meeting. Ten people are in the room, and suddenly, everyone starts shouting different orders at the same time. No one knows who to listen to, work gets doubled, and the whole project becomes a mess.
To fix this, we usually pick one leader. Only that person gives the final orders.
In coding, we have the same problem. Sometimes, having too many "bosses" (objects) causes bugs. We solve this problem using the Singleton Pattern.
Why do we need a single shared instance?
Think about a logging system. Its job is to record what the application is doing, often writing logs to a common destination like a file or a centralized system.
Now imagine if every part of the application creates its own Logger object:
To avoid these problems, we use the Singleton pattern to ensure a single, shared logger instance, providing a centralized and consistent way to handle logging across the application.
The Evolution of the Singleton Pattern
To implement a Singleton, we hide the constructor (to prevent direct instantiation) and provide a global access point. Let’s look at how we can try to solve this in Java
1. The Eager Initialization
class EagerSingleton {
private static final EagerSingleton instance = new EagerSingleton();
private EagerSingleton() {}
public static EagerSingleton getInstance() {
return instance;
}
}
The Downside: If this object is memory heavy and your application never actually ends up using it, you have just wasted precious system memory for nothing.
2. The Lazy Initialization
class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {}
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
The Downside: It is not thread safe. If two different threads enter the if check at the exact same time, both will create a new object.
3. The Synchronized Method
A synchronized block allows only one thread at a time to enter and execute
class SynchronizedSingleton {
private static SynchronizedSingleton instance;
private SynchronizedSingleton() {}
public static synchronized SynchronizedSingleton getInstance() {
if (instance == null) {
instance = new SynchronizedSingleton();
}
return instance;
}
}
The Downside: Even after the instance is initialized, every call to getInstance() still requires acquiring a lock. This unnecessary synchronization introduces performance overhead since locking is only needed during the initial creation phase.
4. Double Checked Locking
class DoubleCheckedSingleton {
private static volatile DoubleCheckedSingleton instance;
private DoubleCheckedSingleton() {}
public static DoubleCheckedSingleton getInstance() {
if (instance == null) { // Check 1
synchronized (DoubleCheckedSingleton.class) {
if (instance == null) { // Check 2
instance = new DoubleCheckedSingleton();
}
}
}
return instance;
}
}
The Downside: While highly performant and thread safe, it is complex to write. Furthermore, It can still be broken using mechanisms like reflection or improper serialization handling.
Recommended by LinkedIn
5. The Enum Singleton
If you want a method that is incredibly simple yet solves all the problems mentioned above, look no further than the Java Enum.
public enum EnumSingleton {
INSTANCE;
public void executeLogging(String message) {
System.out.println("Log: " + message);
}
}
Why this reigns supreme:
How this Works Internally:
A. Class Loading
The JVM loads the EnumSingleton class into memory. Because INSTANCE is a static field, it is initialized during the class loading phase.
B. Thread Safety by Default
The JVM guarantees that a class is only loaded once. It also ensures that all static initialization is finished before any thread can use the class.
The Result: If ten threads try to access INSTANCE at the exact same time for the first time, the JVM makes them wait until the one and only INSTANCE is fully created. This is why you don't need synchronized blocks with Enums.
Why is it "Bulletproof"?
The reason the Enum approach is better than the "Double Checked Locking" method is how it handles two specific "attacks":
No "Reflection" Attacks
Normally, a clever programmer can use Reflection to change a private constructor to public and create a second instance of a Singleton. However, Java’s Constructor.newInstance() method has a specific check. If it sees you are trying to reflectively create an enum, it throws an IllegalArgumentException. The "front door" is physically welded shut.
Built-in Serialization Safety
When we serialize (save) a Java object to a file and later deserialize (load) it back, Java typically creates a new object instance in memory. This behavior can break the Singleton pattern because it results in multiple instances of the same class.
However, Enums handle this differently. Instead of saving the entire object state, Java only stores the name of the enum constant (for example, "INSTANCE"). During deserialization, the JVM does not create a new object. Instead, it looks up the existing enum constant by name and returns the same instance. This ensures that even after serialization and deserialization, the Singleton property is preserved.
That’s all I had to share. Hope you found this insightful and learned something new about the Singleton pattern!😊
Let’s Talk!
Which of these ways do you use in your own projects? Do you prefer the Double Check method or the simple Enum? Let me know in the comments! 👇
I wrote this article as part of my learning journey! If I missed anything or if you have advice on how to improve this, please let me know in the comments. I’d love to hear about other ways to build Singletons too!
Singleton is a great starting point for LLD. The key interview follow-up is always thread safety - double-checked locking vs enum singleton in Java, and why lazy initialization matters when the singleton holds expensive resources like DB connection pools.
If the Singleton pattern ensures one instance per JVM, does that mean it quietly breaks in a microservices world by design 😅 ? Asking because I wonder if we're teaching a pattern that's already architecturally obsolete.
Learned something new 🙂↕️ Looking forward to your next post
Good initiative Ajay Ganesh Patchigolla ✨
Great efforts Ajay Ganesh Patchigolla