Thread Safety in the Singleton Pattern
Thread safety in the Singleton pattern — race conditions, double-checked locking, volatile, holder idiom and enum singletons in Java.
Expected Interview Answer
Thread safety in the Singleton pattern means guaranteeing that when multiple threads call the accessor concurrently before the instance exists, only one instance is ever created and every thread sees a fully-constructed object, which naive lazy initialization does not guarantee.
A plain lazy getInstance() with an “if (instance == null) instance = new X()” check has a race window: two threads can both pass the null check before either finishes construction, creating two instances. Fixing this requires either synchronizing the whole method (correct but slow on every call), double-checked locking with a volatile field (synchronizes only during the rare creation window), the initialization-on-demand holder idiom (relies on the JVM class-loading guarantee to lazily and safely create the instance), or an enum singleton (the JVM guarantees enum constants are created exactly once, even under reflection and serialization attacks). The volatile keyword is essential in double-checked locking because without it, another thread could observe a partially-constructed object due to instruction reordering.
- Guarantees exactly one instance under concurrent access
- Holder idiom gets thread safety without explicit synchronization
- Enum singleton also defends against reflection and serialization
- Prevents subtle race-condition bugs that only appear under load
AI Mentor Explanation
Imagine two team managers both check “do we have a captain yet?” at the exact same moment, both see “no,” and both appoint a different captain — now the team has two captains and chaos follows. Thread-unsafe lazy Singleton creation has this exact race: two threads can both see a null instance and each construct their own. A synchronized appointment process, where only one manager can even ask the question at a time, guarantees exactly one captain is ever named.
Step-by-Step Explanation
Step 1
Identify the race window
Two threads can both see instance == null before either finishes constructing it.
Step 2
Apply double-checked locking
Check null, synchronize, check null again inside the lock, then construct — mark the field volatile.
Step 3
Or use the holder idiom
Rely on the JVM guaranteeing a static inner class is loaded and initialized lazily and thread-safely exactly once.
Step 4
Or use an enum singleton
The JVM guarantees enum constants are instantiated exactly once, even under reflection or serialization attacks.
What Interviewer Expects
- Correct identification of the check-then-act race condition
- Explanation of why volatile is required in double-checked locking
- Knowledge of at least two thread-safe approaches (holder idiom, enum)
- Awareness that naive synchronized-method locking works but hurts performance on every call
Common Mistakes
- Writing double-checked locking without the volatile keyword
- Believing a single null check with no synchronization is safe under concurrency
- Synchronizing the entire getInstance() method unnecessarily after the instance already exists
- Forgetting that reflection can break a naive private-constructor singleton (enum does not have this weakness)
Best Answer (HR Friendly)
“If two threads both try to create the Singleton at the exact same time, a naive check can let both succeed, giving you two instances instead of one. To prevent that, you either synchronize the creation carefully with double-checked locking and a volatile field, or use a pattern like the static holder class or an enum, which the language runtime guarantees is created safely exactly once, even under heavy concurrency.”
Code Example
class DclSingleton {
private static volatile DclSingleton instance;
private DclSingleton() {}
static DclSingleton getInstance() {
if (instance == null) {
synchronized (DclSingleton.class) {
if (instance == null) {
instance = new DclSingleton();
}
}
}
return instance;
}
}
class HolderSingleton {
private HolderSingleton() {}
private static class Holder {
static final HolderSingleton INSTANCE = new HolderSingleton();
}
static HolderSingleton getInstance() {
return Holder.INSTANCE; // JVM guarantees thread-safe, lazy init
}
}Follow-up Questions
- Why must the instance field be declared volatile in double-checked locking?
- How does the initialization-on-demand holder idiom achieve thread safety without locks?
- Why is an enum singleton considered the safest against reflection attacks?
- What is the performance cost of synchronizing the entire getInstance() method?
MCQ Practice
1. What is missing that makes naive double-checked locking unsafe?
Without volatile, instruction reordering can let another thread see a partially-constructed object.
2. The initialization-on-demand holder idiom achieves thread safety by relying on?
The JVM guarantees a class is loaded and initialized exactly once, lazily, on first access — no explicit locking needed.
3. Why is an enum-based Singleton considered especially robust?
Enum singletons are inherently protected from reflective instantiation and serialization creating duplicate instances.
Flash Cards
What race condition threatens naive lazy Singletons? — Two threads both pass the null check before either finishes constructing the instance.
Why is volatile required in double-checked locking? — To prevent another thread from observing a partially-constructed object due to reordering.
What does the holder idiom rely on? — The JVM guarantee that a static inner class initializes lazily and thread-safely exactly once.
Why is enum singleton considered safest? — The JVM guarantees single instantiation even under reflection and serialization.