Singleton in Java
Lazy holder, enum singleton, double-checked locking with volatile, why it hurts testing.
Exactly one instance
A Singleton guarantees a single instance with a global access point. The private constructor stops anyone else calling new. Effective Java's favourite form is an enum with one constant: the JVM guarantees exactly one instance, even against serialization and reflection.
enum Config {
INSTANCE;
String url() { return "db://main"; }
}
// use: Config.INSTANCE.url()Why enums beat classic singletons
A normal singleton has two back doors. Deserializing it can create a second instance, and reflection can call the private constructor. Enum constants are protected from both by the language itself, which is why enum Singleton { INSTANCE; } is the safest form.
The lazy holder idiom
Need lazy creation? The JVM initializes a class on its first active use, exactly once, even with racing threads. Holder isn't touched until get() runs, so the instance is created on demand. get() just reads a static final field: **no synchronized needed**.
class Config {
private Config() {}
private static class Holder {
static final Config I = new Config();
}
static Config get() { return Holder.I; }
}When does the holder wake up?
What does this print?
class Box {
private Box() { IO.println("built"); }
static class Holder {
static final Box I = new Box();
}
static Box get() { return Holder.I; }
}
void main() {
IO.println("start"); Box.get(); Box.get();
}start builtbuilt startstart built built
Show the answer
Nothing is built until the first get() touches Holder, so "start" comes first. Class initialization happens once, so the second get() reuses the same instance. Note: calling some *other* static method of Box would initialize Box only; Holder still waits for get(). That's what makes the idiom lazy.
Broken double-checked locking
This famous idiom is broken because the inst field isn't volatile. The write of the reference can become visible to another thread before the constructor's writes, so that thread may see a half-built object. Fix: private static volatile Registry inst;
private static Registry inst;
static Registry get() {
if (inst == null) {
synchronized (Registry.class) {
if (inst == null)
inst = new Registry();
}
}
return inst;
}Singletons and tests
class PriceService {
int price(String sku) {
return Cache.getInstance().get(sku);
}
}The cache lives for the whole JVM, so one test's data leaks into the next, and you can't swap in a fake.
class PriceService {
private final Cache cache;
PriceService(Cache cache) {
this.cache = cache;
}
}Production passes the one shared cache; each test passes a fresh fake.
On the job
Hidden singletons cause the classic "passes alone, fails in CI" test. Frameworks like Spring keep one shared instance per bean by default, but inject it rather than calling getInstance(). You get "only one" without the global-state pain.
Key takeaways
- enum Config { INSTANCE } is safe against serialization and reflection
- Holder idiom: lazy and thread-safe through class initialization
- Double-checked locking needs a volatile field
- Prefer injecting one shared instance over calling getInstance()
💡 A country has one official mint; everyone uses it, but that makes it hard to test with play money.
Double-checked locking was famously broken in Java until Java 5, when the revised memory model (JSR 133) gave volatile the guarantees that make it safe.
Practice questions
What does this print?
class Lazy {
static { IO.println("outer"); }
private static class H {
static { IO.println("inner"); }
static final Lazy I = new Lazy();
}
static Lazy get() { return H.I; }
static void hi() { IO.println("hi"); }
}
// main runs: Lazy.hi(); Lazy.get();- outer inner hi
- hi outer inner
- outer hi inner
- inner outer hi
Check your answer
outer hi inner. A class is initialized on first active use. Lazy.hi() initializes Lazy only; the nested holder H is initialized later, on the first get(). That's what makes the holder idiom lazy.
Why does Effective Java recommend enum Singleton { INSTANCE; }?
- Enums are faster than classes
- Enum constants can be lazily subclassed
- The JVM guarantees exactly one instance, even against serialization and reflection
- Enums are automatically thread-local
Check your answer
The JVM guarantees exactly one instance, even against serialization and reflection. Deserializing a normal singleton can create a second instance, and reflection can call a private constructor. Enums are protected from both by the language.