The initialization-on-demand holder idiom achieves thread-safe lazy initialization by deferring the singleton's creation to a private static nested class that is only loaded on first use. The JVM's guaranteed-safe class-initialization process provides the synchronization — with zero explicit locking and zero volatile.
The idiom
public class Config {
private Config() { /* expensive init */ }
private static class Holder {
// initialized only when Holder is first referenced
static final Config INSTANCE = new Config();
}
public static Config getInstance() {
return Holder.INSTANCE; // triggers Holder's class init on first call
}
}
Why it's lazy and thread-safe
The JVM does not initialize a class until it is actively used — and Config.Holder is referenced for the first time only inside getInstance(). So new Config() runs lazily, on the first call, not when the outer Config class loads.
The thread-safety comes from the JLS class-initialization guarantee: the JVM holds an internal lock during class initialization, and each class is initialized exactly once. If two threads call getInstance() simultaneously, one initializes Holder while the other blocks on that lock; both then see the same fully-constructed INSTANCE. Because INSTANCE is a static final assigned during class init, it is also safely published — no extra volatile needed.
Versus double-checked locking (DCL)
DCL tries to do the same thing manually:
private static volatile Config instance; // volatile is MANDATORY
public static Config getInstance() {
if (instance == null) { // first check (no lock)
synchronized (Config.class) {
if (instance == null) // second check (locked)
instance = new Config();
}
}
return instance;
}
DCL works, but it has sharp edges: the field must be volatile (without it, a reader can see a non-null reference to a partially-constructed object due to reordering), and the double-check logic is easy to get subtly wrong. The holder idiom achieves the same lazy, lock-free-on-the-hot-path result with less code and no way to misuse it.