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
| Approach | Lazy? | Thread-safe? | Notes |
|---|---|---|---|
Eager static final | ❌ | ✅ | Simplest; class-load cost paid always |
| Synchronized method | ✅ | ✅ | Lock on every call — hot-path overhead |
| Double-checked locking | ✅ | ✅ (with volatile) | Subtle; see next page |
| Holder idiom | ✅ | ✅ free | Recommended default |
| Enum singleton | ❌* | ✅ free | Also serialization/reflection-proof |
Holder Idiom (recommended)
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
| Guarantee | Mechanism |
|---|---|
| Constructor completes before instance published | volatile write after full init |
| Later readers see initialized fields | volatile read establishes ordering |
| Only first callers pay lock | second 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:
| Attack | Enum behavior |
|---|---|
| Serialization | Deserializes named constants — never new objects (spec-guaranteed) |
| Reflection | newInstance() on enums throws IllegalArgumentException |
| Cloning | clone() 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.
Premium Content
Unlock Singleton and all premium lessons with a subscription.
From ₹199.99/year — See plans