Menu

Earn Premium with Referrals

Invite your friends and earn Premium rewards through our referral program.

See how it works and start inviting friends.

Singleton
LLD

Singleton

Understand the Singleton pattern, its implementation, use cases, and common design trade-offs.

Singleton

The Problem It Solves

One connection pool. One config store. One metrics registry. Two instances of these aren’t a style problem — they’re duplicated sockets, split caches, doubled resource consumption. Singleton guarantees one instance per JVM with global access — and the hard part is doing it safely when multiple threads call getInstance() simultaneously.

The Naive Version Fails

class Config {
    private static Config instance;
    private Config() {}
    static Config getInstance() {
        if (instance == null)                    // THREAD A checks → null
            instance = new Config();             // THREAD B checks → null too!
        return instance;                         // two objects, one lost reference
    }
}

Two threads passing the null-check together produce two constructions; one instance leaks with half-initialized state possibly visible to others.

Option Ladder

ApproachLazy?Thread-safe?Notes
Eager static finalSimplest; class-load cost paid always
Synchronized methodLock on every call — hot-path overhead
Double-checked locking✅ (with volatile)Subtle; see next page
Holder idiom✅ freeRecommended default
Enum singleton❌*✅ freeAlso serialization/reflection-proof
class Config {
    private Config() {}

    private static class Holder {
        static final Config INSTANCE = new Config();   // JVM runs this once,
    }                                                  // under class-init lock
    static Config getInstance() { return Holder.INSTANCE; }
}

The trick: inner class Holder is not loaded until first getInstance() call. JVM class initialization is guaranteed thread-safe by the language spec — lazy + safe with zero hand-written synchronization. This exploits the platform instead of fighting it.

Eager Static Final

private static final Config INSTANCE = new Config();

When initialization cost is trivial (most config objects), skip laziness entirely — simplest correct code wins (KISS). Laziness matters only when construction is expensive or depends on runtime state unavailable at class-load time.

Enum Singleton

enum Registry {
    INSTANCE;
    private final Map<String, String> data = new ConcurrentHashMap<>();
}
Registry.INSTANCE.put("k", "v");

Joshua Bloch’s recommendation when you also need protection against reflection/serialization attacks (next pages): enums get both for free from the JVM.

Synchronized Method (why it lingers in interviews)

static synchronized Config getInstance() { ... }

Correct, but every access acquires the lock — after the first call the check is pointless work. Fine for cold paths; measurable contention under load (illustrative: uncontended lock ≈ tens of ns, but contended hot paths degrade far worse).

The Bigger Question Interviewers Now Ask

“Should this be a singleton at all?” Modern answer: usually no — make it a plain object, inject one shared instance via DI container scope=singleton. Testability improves (no hidden global state), and the container handles uniqueness. Hand-rolled singletons persist mainly where frameworks don’t reach.

Failure Modes

  • Hidden mutable global state → test pollution, order-dependent tests.
  • Classloader-per-app environments (old app servers) → “singleton” per classloader = several instances.

The Problem DCL Tries to Solve

Synchronized getInstance() pays lock cost on every call; eager init loses laziness. Double-checked locking attempts both goals: check without locking (fast path), lock only on first construction. Getting it right requires understanding Java’s memory model — which is why this page exists and why interviews love it.

The Broken Version

class Config {
    private static Config instance;              // ← missing volatile: BROKEN

    static Config getInstance() {
        if (instance == null) {                  // 1st check (no lock)
            synchronized (Config.class) {
                if (instance == null) {          // 2nd check (with lock)
                    instance = new Config();     // ← THE PROBLEM LINE
                }
            }
        }
        return instance;
    }
}

Looks airtight; fails before Java 5 because of instruction reordering.

The Race, Step by Step

new Config() decomposes into three operations:

 A: allocate memory          B: allocate memory
 B: run constructor          C: assign instance = ref   ← may reorder!
 C: assign instance = ref    D: (constructor finishes later)

 Thread X executes B→C (reordered). Thread Y:
   sees non-null instance at 1st check → returns it → USES HALF-CONSTRUCTED OBJECT

No lock held by Y, no happens-before edge — Y can observe default field values inside a “non-null” singleton. This shipped in real libraries for years; JDK 1.5’s redefined memory model + volatile semantics fixed it.

The Fixed Version

class Config {
    private static volatile Config instance;     // volatile = the fix

    static Config getInstance() {
        Config local = instance;                 // local read: one volatile read
        if (local == null) {                     // fast path ends here
            synchronized (Config.class) {
                local = instance;
                if (local == null)
                    instance = local = new Config();
            }
        }
        return local;
    }
}

volatile forbids reordering steps across the write and guarantees other threads see fully-constructed state. The local variable trims repeated volatile reads on the hot path (a known micro-optimization from the JDK’s own code).

Happens-Before Intuition

GuaranteeMechanism
Constructor completes before instance publishedvolatile write after full init
Later readers see initialized fieldsvolatile read establishes ordering
Only first callers pay locksecond null-check inside monitor

Modern Verdict

Write DCL only when all three hold: lazy init required, construction genuinely expensive, framework-free codebase. Otherwise:

  • Holder idiom — same laziness/safety, zero subtlety.
  • Enum — same safety plus attack-proofing.
  • DI container singleton scope — no hand-written machinery at all.

Interview reality: asked to “write thread-safe lazy singleton,” producing correct DCL with volatile plus naming holder as the preferred alternative answers both the history and the judgment axes.

Failure Modes Still Possible

  • Forgetting volatile (the classic).
  • Wrapping mutable state carelessly once created — DCL protects initialization, not subsequent concurrent use.

The Problem

Private constructors and one static instance guarantee uniqueness only against ordinary code paths. Java provides two sanctioned ways around both — serialization and reflection — and environments provide a third (multiple classloaders). A singleton that survives them all requires deliberate defenses.

Attack 1: Serialization

// serialize INSTANCE, deserialize → JVM allocates a NEW object,
// bypassing every constructor. Now two Configs exist.
Config copy = (Config) deserialize(serialize(Config.getInstance()));

Deserialization allocates without calling any constructor — your private ctor and static field are irrelevant.

Defense — readResolve:

class Config implements Serializable {
    private static final long serialVersionUID = 1L;
    private static final Config INSTANCE = new Config();
    private Config() {}

    private Object readResolve() {          // JVM calls this after deserialization;
        return INSTANCE;                    // whatever was read is REPLACED by it
    }
}

Attack 2: Reflection

Constructor<Config> c = (Constructor<Config>)
    Config.class.getDeclaredConstructor();
c.setAccessible(true);                       // ignores `private`
Config rogue = c.newInstance();              // second instance, no error

Defense — constructor guard:

private Config() {
    if (INSTANCE != null)
        throw new IllegalStateException("Use getInstance()");
}

The guard works because the static field initializes before any reflective call can run. Note the arms race remains: reflective attackers can strip finals via Field.setAccessible on static fields in some setups — practical rule is to make misuse loud, not theoretically impossible.

The Enum Solution

enum Config {
    INSTANCE;
    // fields & methods here
}

JVM-level guarantees, zero defensive code:

AttackEnum behavior
SerializationDeserializes named constants — never new objects (spec-guaranteed)
ReflectionnewInstance() on enums throws IllegalArgumentException
Cloningclone() final and throwing

This is Effective Java Item 3’s recommendation for attack-resistant singletons; its cost is inability to extend a class and slightly unusual syntax for teams.

Attack 3: Classloaders (environmental)

One JVM ≠ one classloader. App servers / OSGi / hot-reload dev tools load your class twice → two static fields → two “singletons.” No language trick fixes this; the fixes are architectural:

  • Singleton scoped per classloader documented as such.
  • Externalize uniqueness: OS-level daemon, DB row lock, or container-managed lifecycle.

Spring Nuance Worth Naming

Spring’s “singleton” beans are per-container, not per-JVM: two ApplicationContexts yield two instances of the same bean class. GoF singleton and framework scope share vocabulary, not semantics — conflating them in interviews or designs causes real bugs.

Interview Framing

  • Walking attacks 1→2→enum shows depth beyond the standard holder-idiom answer.
  • Closing with “or don’t hand-roll: DI scope” connects classic patterns to current practice.

My Private Notes

Notes are auto-saved locally to this device.