Implement the Singleton pattern in Java so that it is lazily initialised and thread-safe. Why does naive double-checked locking need the volatile keyword? What are the downsides of singletons in general?
Show hints
Consider what another thread can observe while the constructor is still running.
Show the explanation
Model answer. A singleton guarantees one instance of a class and a global access point to it.
1. Naive lazy check (if (instance == null) instance = new Config();) is not thread-safe: two threads can both see null.
2. `synchronized` getter — correct, but every call pays for acquiring the lock, even after initialisation.
3. Double-checked locking (correct form)
public final class Config {
private static volatile Config instance;
private Config() {}
public static Config getInstance() {
Config local = instance;
if (local == null) {
synchronized (Config.class) {
local = instance;
if (local == null) instance = local = new Config();
}
}
return local;
}
}Why `volatile`? instance = new Config() is not a single step: allocate memory, run the constructor, publish the reference. Without volatile, the compiler or CPU may reorder these so that another thread, on its first unsynchronised check, sees a non-null reference to an object whose constructor has not finished — and reads default field values. Under the Java Memory Model (Java 5+), a volatile write creates a happens-before edge, so any thread that reads the non-null reference also sees the fully constructed object.
4. Initialisation-on-demand holder (simpler, preferred)
private static class Holder { static final Config INSTANCE = new Config(); }
public static Config getInstance() { return Holder.INSTANCE; }The JVM initialises Holder only on first use, and class initialisation is guaranteed thread-safe, so no explicit locking is needed.
5. Enum singleton — enum Config { INSTANCE; } is thread-safe and also resists creating extra instances through serialization or reflection.
(In C++11 and later, a function-local static object is initialised thread-safely, giving an equally simple lazy singleton.)
Downsides: hidden global state, hard to mock in unit tests, and a possible contention point for mutable shared state. Many teams prefer dependency injection — create one instance at startup and pass it where needed.
Follow-up: "Can reflection break a class-based singleton?" Yes — setAccessible(true) on the private constructor allows a second instance unless the constructor throws when an instance already exists; the enum approach blocks this.
Original UniCircle question. Last reviewed 28 Oct 2026.
