Singleton Advanced: Risk & Fixes
Double-Checked Locking
In the previous example, using synchronized on the entire getInstance() method works, but itโs expensive. Every time a thread asks for the instance, it has to wait in line, even after the instance has already been created. To solve this Double-Checked Locking pattern is the “gold standard” for high-performance Singletons in multi-threaded environments.
Double-checked locking solves this by only locking the first time. To do this correctly, you must use the volatile keyword. This ensures that multiple threads handle the instance variable correctly as it is being initialized.
public final class Singleton {
// The 'volatile' keyword is critical here!
// It prevents instruction reordering and ensures visibility across threads.
private static volatile Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
// First check (no locking)
if (instance == null) {
// Synchronize only the first time
synchronized (Singleton.class) {
// Second check (inside the lock)
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
Why the “Double” Check Matters?
- The First Check: If the instance already exists, we return it immediately. No threads have to wait for a lock. This makes the code fast.
- The Synchronized Block: If two threads pass the first check at the exact same time, they both try to enter the synchronized block. One wins, one waits.
- The Second Check: The thread that was waiting enters the block after the first thread finishes. Without this second check, it would create a second instance of the Singleton.
The Role of volatile
In Java, the JVM sometimes reorders instructions to save time. Without volatile, a thread could theoretically see a “half-initialized” objectโan instance that exists in memory but hasn’t finished running its constructor. volatile acts as a guardrail, ensuring the object is fully baked before any other thread touches it.
A Better Modern Alternative: The Bill Pugh Singleton
While double-checked locking is great for interviews and understanding memory models, most modern Java developers use the Bill Pugh Singleton Implementation. It uses a “Static Inner Helper Class” to handle thread safety via the ClassLoader, which is inherently thread-safe and more readable.
1. The Bill Pugh Singleton (Static Inner Class)
This is widely considered the best way to implement a Singleton in Java. It doesn’t use synchronized or volatile, making it incredibly fast. It relies on the way the JVM loads classes.
public class BillPughSingleton {
private BillPughSingleton() {}
// Static inner class - not loaded into memory until getInstance() is called
private static class SingletonHelper {
private static final BillPughSingleton INSTANCE = new BillPughSingleton();
}
public static BillPughSingleton getInstance() {
return SingletonHelper.INSTANCE;
}
}
Why it works: The SingletonHelper class isn’t loaded until someone calls getInstance(). Because class loading is guaranteed to be thread-safe by the JVM, this is 100% thread-safe without any locking overhead.
2. The “Singleton Killer”: Reflection
Even with a private constructor, an attacker (or a curious developer) can use Java Reflection to change the constructor’s accessibility and create a second instance, “breaking” your Singleton.
// How to break it
Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
// Making private constructor public!
Singleton instanceTwo = constructor.newInstance();
The Fix: Reflection-Proofing
To prevent this, you can add a check inside your private constructor. If an instance already exists, throw an exception.
private Singleton() {
if (instance != null) {
throw new RuntimeException("Use getInstance() method to get the single instance of this class.");
}
}
3. The Ultimate Solution: The Enum Singleton
Joshua Bloch (author of Effective Java) argues that a single-element Enum is the best way to implement a Singleton.
- It is inherently thread-safe.
- It is protected against Reflection attacks.
- It handles Serialization automatically (Standard Singletons can “break” during the de-serialization process if you don’t handle the readResolve() method).
public enum EnumSingleton {
INSTANCE;
public void doSomething() {
System.out.println("Enum Singleton in action!");
}
}
Usage:
EnumSingleton.INSTANCE.doSomething();
Summary Table
| Method | Thread-Safe? | Lazy Loaded? | Performance | Complexity |
| Lazy (Synchronized) | Yes | Yes | Slow | Low |
| Double-Checked Locking | Yes | Yes | Fast | High |
| Bill Pugh (Inner Class) | Yes | Yes | Fastest | Low |
| Enum | Yes | No | Fastest | Lowest |
To build a truly bulletproof Singleton in Java, you must look beyond the code itself and consider how the Java platform behaves during Serialization and within Frameworks.
1. The Serialization “Breaker”
If your Singleton implements Serializable, itโs at risk. When you save a Singleton to a file (Serialization) and then read it back (Deserialization), the JVM creates a brand-new instance of the class by reading the byte stream, bypassing your private constructor and the getInstance() logic.
The Fix:
readResolve()
To prevent this, you must implement the readResolve() method. This special “hook” is called by the JVM during deserialization, allowing you to replace the newly created object with your existing Singleton instance.
public class SerializableSingleton implements Serializable {
private static final long serialVersionUID = 1L;
private static final SerializableSingleton instance = new SerializableSingleton();
private SerializableSingleton() {}
public static SerializableSingleton getInstance() {
return instance;
}
// This method is the "magic fix"
protected Object readResolve() {
return instance; // Return the existing instance, discarding the new one
}
}
2. Spring Framework: Singleton Scope
In Spring, “Singleton” refers to the Scope, not necessarily the design pattern. While a GoF Singleton is one instance per ClassLoader, a Spring Singleton is one instance per ApplicationContext.
- How it works: Spring uses a DefaultSingletonBeanRegistry (essentially a ConcurrentHashMap) to store bean instances.
- Default Behavior: If you annotate a class with @Service or @Component, it defaults to Singleton.
- Injection: When you use @Autowired, Spring doesn’t call getInstance(); it looks up the object in its internal Map and gives you the reference.
@Service // Defaults to Singleton scope
public class PaymentService {
public void process() {
/* logic */
}
}
3. AEM (Adobe Experience Manager): OSGi Services
In AEM, services are managed by the OSGi container (Apache Felix). Most AEM services you write are Singletons by default.
- Service Scope: When you use the @Component annotation, the OSGi framework ensures that only one instance of that service is active in the bundle.
- The OSGi Registry: Much like Spring, OSGi keeps a registry. When a Servlet or another Service needs your logic, the framework “injects” the single active instance.
@Component(service = MySingletonService.class, immediate = true)
public class MySingletonServiceImpl implements MySingletonService {
// OSGi manages this as a singleton service within the container
}
This Singleton Design Pattern Cheat Sheet summarizes the evolution of the pattern, from basic implementation to “bulletproof” production-grade code.
| Method | Best For | Pros | Cons |
| Eager | Simple apps | Simplest; Thread-safe. | Instance created even if never used. |
| Lazy (Sync) | Low traffic | Easy to write. | Performance hit due to synchronized. |
| Double-Checked | Legacy/High traffic | High performance. | Complex; requires volatile. |
| Bill Pugh | Modern Java | Fast; Lazy; Thread-safe. | None (for standard Java). |
| Enum | Security/API | Reflection & Serial safe. | No Lazy loading; no inheritance. |



Post Comment