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).
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:
- Serialization: The Java serialization spec (§1.12) special-cases enums. It serializes only the constant's name; on deserialization it calls
Enum.valueOf()to resolve the existing constant. No new instance is ever created. NoreadResolve()needed. - Reflection:
Constructor.newInstance()contains an explicit check — if the class is an enum, it throwsIllegalArgumentException("Cannot reflectively create enum objects"). The back door is hard-coded shut in the JVM.
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.
| Variant | Thread-safe? | Lazy? | Reflection-safe? | Serialization-safe? | Can extend a class? |
|---|---|---|---|---|---|
Eager static final | Yes (class init) | No | No (needs constructor guard) | No (needs readResolve()) | Yes |
DCL (volatile) | Yes | Yes | No (needs constructor guard) | No (needs readResolve()) | Yes |
| Bill Pugh holder | Yes (inner class init) | Yes | No (needs constructor guard) | No (needs readResolve()) | Yes |
| Enum | Yes (class init) | No | Yes (JVM-enforced) | Yes (JVM-enforced) | No |
Pitfalls
- DCL without
volatileis subtly broken — the classic memory-model bug. - Reflection / serialization can create a second instance even when the constructor is private. The holder and DCL variants need explicit guards (
readResolve()+ constructor check); only enum is immune by specification. - Singletons are global mutable state — they hurt testability; often dependency injection is the better answer. Use deliberately.
Takeaways
- Naive lazy init is a race; pick the holder idiom or enum.
- Double-checked locking needs
volatile(visibility of the constructed object). - The holder and DCL are breakable by deserialization (
readResolve()defends) and reflection (constructor guard defends). Enum is immune to both — that is its real advantage over brevity. - Singleton = global state — convenient, but consider DI for testability.
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.
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.
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.
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.
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.