Singleton Pattern
Overview
The Singleton Pattern ensures that a class has only one instance and provides a global point of access to it. It is used for shared resources where multiple instances would cause data inconsistency or wasted memory.
Common Use Cases: Thread pools, Database connection pools, Configuration managers, and Loggers.
Implementation Strategies in Java
1. Eager Initialization
The instance is created when the class is loaded. It's thread-safe but doesn't support lazy loading.
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {} // Private constructor prevents external instantiation
public static Singleton getInstance() { return INSTANCE; }
}
2. Double-Checked Locking (DCL)
Combines lazy loading with thread efficiency. This is the most prevalent pattern in production.
public class Singleton {
// volatile is critical to prevent instruction reordering
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) { // First check (Performance)
synchronized (Singleton.class) {
if (instance == null) { // Second check (Safety)
instance = new Singleton();
}
}
}
return instance;
}
}
Why is volatile mandatory?
The operation new Singleton() is not atomic at the JVM level. It involves:
- Allocating memory.
- Initializing the object.
- Assigning the reference to the pointer.
If the JIT compiler reorders these to 1 $\rightarrow$ 3 $\rightarrow$ 2, a second thread could see a non-null but uninitialized object during the first
if (instance == null), causing a crash.
3. Static Inner Class (Holder)
Leverages Java's class-loading mechanism to achieve lazy-loading without explicit synchronization.
public class Singleton {
private Singleton() {}
private static class Holder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return Holder.INSTANCE; // Holder is only loaded when this is first called
}
}
4. Enum (The Ultimate Solution)
Joshua Bloch (author of Effective Java) recommends this. It is immune to reflection and serialization attacks.
public enum Singleton {
INSTANCE;
public void doSomething() { /* Business logic */ }
}
Comparison Matrix
| Strategy | Lazy Loading | Thread Safe | Serialization Safe | Reflection Safe | Recommended |
|---|---|---|---|---|---|
| Eager | ❌ | ✅ | ❌ | ❌ | ⭐ |
| DCL | ✅ | ✅ | ❌ | ❌ | ⭐⭐⭐ |
| Inner Class | ✅ | ✅ | ❌ | ❌ | ⭐⭐⭐⭐ |
| Enum | ❌ | ✅ | ✅ | ✅ | ⭐⭐⭐⭐⭐ |
Deep Technical Insights
Breaking Singletons
Standard implementations (except Enums) can be broken via Reflection (constructor.setAccessible(true)). To prevent this, add a check inside the private constructor to throw an exception if the INSTANCE is already initialized. Similarly, for Serialization, you must implement the readResolve() method to return the existing instance instead of creating a new one.
The Problem with Singletons
In modern unit testing, singletons can be problematic because they maintain state across tests (acting as a "global variable"). Dependency Injection (DI) frameworks like Spring manage "Singletons" (beans) at the container level, providing the benefits without the architectural rigidity of a hard-coded class pattern.