Definition
- Singleton pattern – ensures a class has exactly one instance across the whole application and provides a single global access point.
- Thread safety – in a multi-threaded program, two threads must not be able to create two instances at the same time.
- Eager – instance created at class load; safe but not lazy.
- Synchronized getter – lazy but locks on every call.
- Double-checked locking – locks only on first creation; requires
volatileto prevent instruction reordering. - Bill Pugh holder – lazy + safe via JVM class-initialization guarantee; no synchronization needed.
- Enum singleton – recommended by Effective Java; immune to serialization, reflection, and cloning.
The National Bank of India has exactly one central vault. There is no second vault, and every branch that needs the vault uses the same one through a single controlled door. If two staff members tried to build a vault at the same moment, you could end up with two vaults, which must never happen. Making sure only one vault ever exists, even when many staff rush the door together, is exactly what thread-safe Singleton creation guarantees.
Understanding the Concepts
What is the Singleton pattern?
Singleton ensures a class has exactly one instance and provides a global access point. In banking, this is used for shared configuration registries, audit ledgers, and connection pools where two instances would cause inconsistent state.
How does eager initialization work?
The instance is created when the class loads. It is inherently thread-safe because the JVM initialises a class once under a lock.
package com.bank.config;
public class ConfigRegistry {
// Eager: created at class load
private static final ConfigRegistry INSTANCE = new ConfigRegistry();
private ConfigRegistry() {
System.out.println("ConfigRegistry created");
}
public static ConfigRegistry getInstance() {
return INSTANCE;
}
public String getInterestRate(String type) {
return "0.04"; // loaded from config
}
}
Downside: not lazy. The instance is built even if never used, wasting resources if construction is expensive.
How does the synchronized getter work?
Lazy and correct, but every single call acquires the lock, even after the instance exists.
package com.bank.config;
public class ConfigRegistrySync {
private static ConfigRegistrySync instance;
private ConfigRegistrySync() {}
// Lazy but locks EVERY call
public static synchronized ConfigRegistrySync getInstance() {
if (instance == null) {
instance = new ConfigRegistrySync();
}
return instance;
}
}
Under heavy concurrent reads, this becomes a bottleneck.
How does double-checked locking work?
Locks only during the first creation. The field must be volatile.
package com.bank.config;
public class ConfigRegistryDCL {
// MUST be volatile to prevent instruction reordering
private static volatile ConfigRegistryDCL instance;
private ConfigRegistryDCL() {}
public static ConfigRegistryDCL getInstance() {
if (instance == null) { // first check (no lock)
synchronized (ConfigRegistryDCL.class) {
if (instance == null) { // second check (with lock)
instance = new ConfigRegistryDCL();
}
}
}
return instance;
}
}
Without volatile, the JVM may reorder: allocate memory, assign reference, then construct. Another thread sees a non-null reference pointing to a partially-constructed object.
How does the Bill Pugh holder idiom work?
A private static inner holder class contains the instance. The holder is loaded only when getInstance() first references it. JVM class-initialization is thread-safe by specification.
package com.bank.config;
public class ConfigRegistryBillPugh {
private ConfigRegistryBillPugh() {
System.out.println("ConfigRegistry created (Bill Pugh)");
}
// Holder is loaded only when getInstance() is called
private static class Holder {
static final ConfigRegistryBillPugh INSTANCE = new ConfigRegistryBillPugh();
}
public static ConfigRegistryBillPugh getInstance() {
return Holder.INSTANCE;
}
}
Lazy, thread-safe, no synchronized, no volatile. This is the top recommendation for class-based Singletons.
How does the enum singleton work?
The JVM guarantees a single instance of each enum constant. Enums are automatically safe against serialization, reflection, and cloning.
package com.bank.config;
import java.math.BigDecimal;
import java.util.Map;
public enum InterestRateRegistry {
INSTANCE;
private final Map<String, BigDecimal> rates = Map.of(
"SAVINGS", new BigDecimal("0.04"),
"CURRENT", new BigDecimal("0.02"),
"NRE", new BigDecimal("0.065")
);
public BigDecimal getRate(String accountType) {
return rates.getOrDefault(accountType, BigDecimal.ZERO);
}
}
// Usage:
// BigDecimal rate = InterestRateRegistry.INSTANCE.getRate("SAVINGS");
Joshua Bloch in Effective Java calls the single-element enum the best way to implement a Singleton. Its only limitation is that an enum cannot extend a class.
How do you defend a class-based Singleton against duplication?
Three routes can create a second instance: cloning, serialization, and reflection.
package com.bank.config;
import java.io.Serializable;
public class SecureSingleton implements Serializable {
private static final SecureSingleton INSTANCE = new SecureSingleton();
// Defense 1: private constructor + reflection guard
private SecureSingleton() {
if (INSTANCE != null) {
throw new IllegalStateException("Already created");
}
}
public static SecureSingleton getInstance() {
return INSTANCE;
}
// Defense 2: serialization – return existing instance
private Object readResolve() {
return INSTANCE;
}
// Defense 3: cloning – refuse
@Override
protected Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException("Singleton cannot be cloned");
}
}
The enum eliminates all three for free.
Which approach should you use?
| Approach | Lazy | Thread-safe | Serialization-safe | Reflection-safe |
|---|---|---|---|---|
| Eager | No | Yes | No | No |
| Synchronized | Yes | Yes | No | No |
| Double-checked | Yes | Yes (volatile) | No | No |
| Bill Pugh holder | Yes | Yes | No | No |
| Enum | Yes | Yes | Yes | Yes |
Use the enum when you just need one instance with no inheritance. Use the Bill Pugh holder when you need a private constructor with special construction logic or need to extend a class.
What should a senior engineer know?
- A JVM Singleton is one per JVM, not one per cluster. Across a cluster, the source of truth must be a shared store like a database or Redis.
- In Spring, beans are singletons by default via the container, not via the Singleton pattern.
- Singletons make unit testing harder because they carry global state. Prefer dependency injection in production code.
package com.bank.demo;
import java.math.BigDecimal;
public class SingletonDemo {
public static void main(String[] args) {
// Enum singleton usage
BigDecimal rate = InterestRateRegistry.INSTANCE.getRate("SAVINGS");
System.out.println("Savings rate: " + rate);
// Bill Pugh usage
ConfigRegistryBillPugh config = ConfigRegistryBillPugh.getInstance();
System.out.println("Config loaded: " + (config != null));
}
}
Savings rate: 0.04
ConfigRegistry created (Bill Pugh)
Config loaded: true
Visual Overview
Singleton Approaches – Thread Safety
─────────────────────────────────────
Eager:
Class load ──▶ INSTANCE created ──▶ getInstance() returns it
(always, even if unused)
Synchronized:
getInstance() ──▶ LOCK ──▶ null check ──▶ create if needed ──▶ return
(locks every call)
Double-Checked Locking:
getInstance() ──▶ null check (no lock)
│
null? ──▶ LOCK ──▶ null check again ──▶ create
not null? ──▶ return (fast path)
(volatile prevents partial construction)
Bill Pugh Holder:
getInstance() ──▶ references Holder class
│
JVM loads Holder ──▶ creates INSTANCE (once, thread-safe)
(no explicit lock in your code)
Enum:
InterestRateRegistry.INSTANCE
──▶ JVM guarantees one instance
──▶ immune to clone, serialization, reflection
Key Takeaway
The Singleton pattern ensures a class has exactly one instance for the whole application and gives a single global access point. The hard part in Java is making sure that when many threads call getInstance at the same moment, they cannot accidentally create two instances.
There are five common ways. Eager initialization creates the instance in a static final field at class load; it is thread-safe but not lazy. The synchronized getter is lazy and correct but locks on every call. Double-checked locking checks null without a lock, then locks and checks again; it must use a volatile field because without it instruction reordering can publish a reference to a partially-constructed object.
The two Rajesh prefers are the Bill Pugh holder idiom and the enum. The Bill Pugh idiom puts the instance in a private static inner holder class, so it is created only when getInstance first touches the holder, and the JVM's class-initialization guarantee makes it both lazy and thread-safe with no synchronized or volatile. The enum declares a single constant, and the JVM guarantees one instance; crucially the enum is also safe against serialization, reflection, and cloning, which is why Effective Java recommends it.
At National Bank of India, Pankaj uses the enum for the shared interest-rate registry and the Bill Pugh holder for the audit configuration that needs a private constructor with special setup. He also remembers that a JVM Singleton is one per JVM, so across a cluster the real source of truth must be a shared store like Redis.
Remember one sentence: use the enum singleton for free safety against serialization, reflection, and cloning, and the Bill Pugh holder when you need lazy class-based construction.
---