Loading Now

Singleton Advanced: Risk & Fixes

single-advanced

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?

  1. 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.
  2. 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.
  3. 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

MethodThread-Safe?Lazy Loaded?PerformanceComplexity
Lazy (Synchronized)YesYesSlowLow
Double-Checked LockingYesYesFastHigh
Bill Pugh (Inner Class)YesYesFastestLow
EnumYesNoFastestLowest

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.

MethodBest ForProsCons
EagerSimple appsSimplest; Thread-safe.Instance created even if never used.
Lazy (Sync)Low trafficEasy to write.Performance hit due to synchronized.
Double-CheckedLegacy/High trafficHigh performance.Complex; requires volatile.
Bill PughModern JavaFast; Lazy; Thread-safe.None (for standard Java).
EnumSecurity/APIReflection & Serial safe.No Lazy loading; no inheritance.