Knowledge Guide
HomeOO & Low-Level DesignCreational

Singleton — Thread-Safe Variants (DCL, Holder, Enum)

One instance — and why the naive version is broken

The Singleton ensures a class has exactly one instance with a global access point (a config, a connection pool). The textbook "lazy" version is a concurrency bug: under multiple threads, two callers can both see instance == null and each construct one — the same read-modify-write race as counter++ (see Concurrency → Race Conditions).

Two threads both check instance==null, both see null, and both construct a Singleton, producing two instances
Two threads both check instance==null, both see null, and both construct a Singleton, producing two instances

The variants, worst to best

// 1. Eager — thread-safe (JVM inits once), but built even if never used
private static final Singleton I = new Singleton();

// 2. Synchronized method — correct but locks on EVERY call (slow)
static synchronized Singleton get() { if (I==null) I=new Singleton(); return I; }

// 3. Double-checked locking — lock only on first init. 'volatile' is MANDATORY:
//    without it another thread can see a non-null but half-constructed object (memory-model reordering).
private static volatile Singleton I;
static Singleton get() {
    if (I == null) {                       // no lock on the hot path
        synchronized (Singleton.class) {
            if (I == null) I = new Singleton();   // re-check under lock
        }
    }
    return I;
}

// 4. Bill Pugh holder idiom — lazy + thread-safe with NO synchronization (classloader guarantees it)
private static class Holder { static final Singleton I = new Singleton(); }
static Singleton get() { return Holder.I; }

// 5. Enum — Joshua Bloch's pick: concise, serialization- and reflection-safe
enum Singleton { INSTANCE; void work() {} }

Use the holder idiom (or enum) by default; reach for double-checked locking only when init depends on runtime parameters. Note how this ties straight back to the memory model: volatile here is about visibility of a fully-constructed object, not just atomicity.

Hardening the holder and DCL against deserialization and reflection

The holder and DCL variants you should default to are still breakable two ways at runtime. The private constructor stops new at compile time, but the JVM has back doors that bypass it entirely. Here is how to close them — or why to use enum instead.

Attack 1 — Serialization creates a second instance

When a class implements Serializable and is deserialized, ObjectInputStream.readObject() allocates a new object without calling the constructor. The result is two objects that both believe they are the singleton.

Defense: readResolve(). Add this method to the singleton class so the serialization framework discards the deserialized copy and returns the canonical instance:

// Bill Pugh holder — with serialization defense
class Singleton implements Serializable {
    private static final long serialVersionUID = 1L;

    private Singleton() {
        // reflection guard — see below
        if (Holder.INSTANCE != null) {
            throw new IllegalStateException(
                "Use get() — reflection not allowed");
        }
    }

    private static class Holder {
        static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton get() { return Holder.INSTANCE; }

    // Serialization defense: discard the deserialized copy
    private Object readResolve() {
        return Holder.INSTANCE;
    }
}

Without readResolve(), every round-trip through serialization silently produces a duplicate. The deserialized copy is allocated momentarily but readResolve() replaces it with the real instance before the caller ever sees it.

Attack 2 — Reflection calls the private constructor

The Reflection API can make any constructor accessible at runtime:

Constructor<Singleton> ctor = Singleton.class.getDeclaredConstructor();
ctor.setAccessible(true);       // bypass private
Singleton rogue = ctor.newInstance();  // second instance!

Defense: guard the constructor. The private constructor checks whether the holder has already been initialized. If so, it throws — turning the reflection path into a hard error. This is shown in the complete code above: if (Holder.INSTANCE != null) throw new IllegalStateException().

This is a convention-level guard: a truly determined caller using sun.misc.Unsafe can still circumvent it. But it catches accidental misuse, makes the intent explicit, and is the standard recommendation from Effective Java.

Why enum needs neither guard

The enum singleton (enum Singleton { INSTANCE; }) is immune to both attacks — not by convention but by the JVM specification:

That immunity — not brevity — is the real reason enum Singleton { INSTANCE } is the recommended default when you do not need lazy runtime-parameterized initialization or inheritance from a base class.

When enum is NOT the answer

Enum singletons cannot extend a class (enums implicitly extend java.lang.Enum), and they do not support lazy initialization — the instance is created at class-loading time. If you need inheritance or true create-on-first-use, use the holder idiom hardened with the two guards above.

VariantThread-safe?Lazy?Reflection-safe?Serialization-safe?Can extend a class?
Eager static finalYes (class init)NoNo (needs constructor guard)No (needs readResolve())Yes
DCL (volatile)YesYesNo (needs constructor guard)No (needs readResolve())Yes
Bill Pugh holderYes (inner class init)YesNo (needs constructor guard)No (needs readResolve())Yes
EnumYes (class init)NoYes (JVM-enforced)Yes (JVM-enforced)No

Pitfalls

Takeaways


Re-authored for this guide; race diagram hand-authored as SVG. Follows Effective Java (Bloch) Item 3 and refactoring.guru. Added: concrete serialization/reflection hardening for the holder idiom, the constructor guard pattern, why enum immunity is JVM-spec-level, and the variant comparison table. See also: (Concurrency) Race Conditions, Memory Model & Visibility.

🤖 Don't fully get this? Learn it with Claude

Stuck on Singleton — Thread-Safe Variants (DCL, Holder, Enum)? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **Singleton — Thread-Safe Variants (DCL, Holder, Enum)** (OO & Low-Level Design) and want to truly understand it. Explain Singleton — Thread-Safe Variants (DCL, Holder, Enum) from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **Singleton — Thread-Safe Variants (DCL, Holder, Enum)** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **Singleton — Thread-Safe Variants (DCL, Holder, Enum)** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **Singleton — Thread-Safe Variants (DCL, Holder, Enum)** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes