Singleton pattern — 4 ways to implement it, which is thread-safe?
MediumEager static field: simple, thread-safe. Lazy null check: broken under threads. Double-checked locking: safe only with volatile. Holder class or enum: lazy or simple, thread-safe with no locking. The enum version also survives serialization and reflection.
How it works
Four of these are thread-safe; the plain lazy version is the trap interviewers want you to spot.
- Eager.
private static final Config INSTANCE = new Config();The JVM runs static initializers once, under a lock, so it's thread-safe. Downside: created when the class initializes, even if never used. - Lazy with a plain null check.
if (instance == null) instance = new Config();Two threads can both seenulland build two instances. Not thread-safe. Making the methodsynchronizedfixes it but takes a lock on every call. - Double-checked locking. Check, lock, check again, create. Safe only if the field is
volatile; otherwise another thread can see the reference before the constructor's writes. - Initialization-on-demand holder. A private static nested class holds the instance. It isn't initialized until
getInstance()first touches it, and class initialization is thread-safe. Lazy, no locks, novolatile. - Enum.
enum Config { INSTANCE; }The JVM guarantees one instance, and both reflection and serialization are blocked from creating a second.
Example
// 3. Double-checked locking: volatile is required
final class RateLimiter {
private static volatile RateLimiter instance;
private RateLimiter() {}
static RateLimiter get() {
RateLimiter local = instance; // one volatile read on the fast path
if (local == null) {
synchronized (RateLimiter.class) {
local = instance;
if (local == null) instance = local = new RateLimiter();
}
}
return local;
}
}
// 4. Holder: lazy because Holder isn't initialized until get() runs
final class FeatureFlags {
private FeatureFlags() {}
private static final class Holder { static final FeatureFlags FLAGS = new FeatureFlags(); }
static FeatureFlags get() { return Holder.FLAGS; }
}
// 5. Enum: shortest and hardest to break
enum IdGenerator {
INSTANCE;
private final AtomicLong next = new AtomicLong();
long nextId() { return next.incrementAndGet(); }
}Edge cases
- Reflection can call a private constructor with
setAccessible(true). Guard it by throwing if an instance already exists, or use an enum, where reflective construction throws. - Deserializing a
Serializablesingleton creates a new object unless you addreadResolve()returning the instance. Enums handle this automatically. - "Singleton" means one per class loader. Two class loaders loading the same class give two instances.
- An enum singleton can't extend another class (it can implement interfaces).
Common mistakes
- Double-checked locking without
volatile. - Using a singleton as a global mutable bag of state, which makes tests order-dependent and hard to isolate.
- Synchronizing the whole
getInstance()"to be safe" on a hot path.
Likely follow-up
"Is a Spring singleton bean the same thing?" No. Spring's singleton scope means one instance per application context, managed by the container, and the class itself can still be constructed elsewhere. Thread safety of its state is still your job.
Get every deep dive in the app
Coming soon to the App StoreComing soon to Google Play