Singleton Pattern
Ensure a class has exactly one instance and provide a global access point to it.
Why It Matters
- Resource control — Prevents multiple instances of an expensive object (DB connection pool, thread pool).
- Consistency — Every caller observes the same state — useful for caches and counters.
- Lazy creation — Some implementations only create the instance the first time it is requested.
- Global access — No need to thread a reference through 10 layers of method calls.
In Layman's Terms
Imagine the President of a country: at any moment there is exactly one. Anyone in the country can dial a single hotline ("getInstance()") and reach that one person. You can't spawn a second President with new.
Other examples: the print spooler in your laptop (one queue), the file system in Java (File-system view of the OS), or Spring's ApplicationContext for a given app.
Real-World Analogy
🖨 Print Spooler Analogy
Operating systems typically have one print spooler — a central queue every program adds documents to. If two spoolers existed, they'd fight over the printer and you'd get garbled pages. The OS therefore makes the spooler a singleton: one instance, accessible globally.
Singletons fit naturally where the *resource itself* is unique (the printer port, the application config file, the in-memory cache).
Visual
┌──────────┐ ┌──────────────────┐
│ Client A │ ──getInstance─▶ │ │
└──────────┘ │ │
┌──────────┐ │ ▣ Singleton │
│ Client B │ ──getInstance─▶ │ (only ONE) │
└──────────┘ │ │
┌──────────┐ │ │
│ Client C │ ──getInstance─▶ │ │
└──────────┘ └──────────────────┘
▲
all clients receive the
same reference (==) ┌────────────────────────────────────┐
│ Singleton │
├────────────────────────────────────┤
│ - instance : Singleton ◀──── static, private
│ - Singleton() ◀──── private ctor
├────────────────────────────────────┤
│ + getInstance() : Singleton ◀── only public way in
│ + doWork() │
└────────────────────────────────────┘Top — runtime view; many clients all pointing at the one shared instance. Bottom — UML showing the static instance + private constructor.
When to Use
- Truly shared, expensive resources: connection pools, thread pools, caches.
- Application-wide configuration loaded once on startup.
- Logger that funnels output to a single sink (file / stdout).
- Hardware-coupled state: a printer driver, a sensor proxy.
- Coordinator objects where having more than one would corrupt invariants (e.g., ID generator, license manager).
When NOT to Use
- When you simply want global access — globals != singletons. Pass dependencies in the constructor instead.
- When you need multiple configurations (test vs prod) — the singleton "happens" to live in static state and breaks isolation.
- For mutable state that callers will write to from multiple threads — you're inviting race conditions.
- When you can't tolerate hidden coupling in tests — singletons leak between test cases unless reset.
- When dependency-injection containers (Spring, Guice, Dagger) are available — let the container manage the lifecycle. You get singleton scope without the pattern's downsides.
Anti-Pattern (What NOT to Do)
getInstance() everywhereBadjavaclass ReportService {
void run() {
DbPool.getInstance().conn(); // hidden dependency
}
}The Right Way
@Service // Spring: one bean per context
class ReportService {
private final DbPool pool;
ReportService(DbPool pool) { this.pool = pool; }
}@MockBean.Pros & Cons
- Single instance guaranteed — no accidental duplicates of expensive resources.
- Lazy creation possible — pay only when first used (Bill Pugh / DCL variants).
- Globally reachable without passing references through every layer.
- Useful for natural singletons: spoolers, OS handles, hardware controllers.
- Hidden global state — methods that look pure secretly read/write a singleton.
- Hard to unit test — tests inherit state from previous tests, must reset manually.
- Hides dependencies — code that uses
X.getInstance()does not advertise that it depends on X. - Breaks Single Responsibility — the class manages both its job *and* its lifecycle.
- Concurrency pitfalls — DCL is famously easy to get wrong without
volatile. - Subclassing / mocking is awkward — the constructor is private.
Singleton vs. Static Utility vs. Spring Bean
| Aspect | Singleton | Static Utility | Spring `@Component` (singleton scope) |
|---|---|---|---|
| Has state | Yes — instance fields | No (or static fields) | Yes — instance fields |
| Polymorphism | Possible (implements interface) | Not really | Yes — DI uses interfaces |
| Lazy init | Yes (Bill Pugh / DCL) | No — class load | Configurable per bean |
| Mockable in tests | Painful | Painful | Easy — inject a stub |
| Lifecycle owner | Itself | JVM class loader | Spring container |
| When to pick | Resource you control entirely | Pure functions / helpers | Almost always, when you have a DI container |
Common Confusions Cleared Up
- Static classes can't implement interfaces / extend a base — Singletons can. Useful for swapping implementations.
- Static classes have no instance state — pure functions only. Singletons can hold caches, counters, queues.
- Static classes can't be passed as a parameter — Singletons can (treat them as objects).
- Singletons can be lazy; static classes initialize at first reference and can't be deferred.
- Spring beans are singletons per ApplicationContext, not per JVM. In tests you spin up a fresh context — beans reset automatically.
- Spring beans are wired by interface and easily mocked. Classic singletons hard-code
getInstance()everywhere. - Prefer Spring (or any DI) singleton scope; it gives you the same uniqueness guarantee without the testing pain.
- Monostate hides static fields behind a normal class — every instance shares the same data. Caller uses
new Foo()but gets the same state. Less surprising thangetInstance()but rarely used in practice.
Variations / Implementations
1️⃣ Eager initialization
Instance is built once when the class loads. Thread-safe for free, but pays the cost even if you never call it.
public final class EagerSingleton {
private static final EagerSingleton INSTANCE = new EagerSingleton();
private EagerSingleton() {}
public static EagerSingleton getInstance() {
return INSTANCE;
}
}2️⃣ Lazy initialization (NOT thread-safe)
Builds on first call — but two threads racing can each pass the null check and create two instances.
public final class LazyUnsafeSingleton {
private static LazyUnsafeSingleton instance;
private LazyUnsafeSingleton() {}
public static LazyUnsafeSingleton getInstance() {
if (instance == null) {
instance = new LazyUnsafeSingleton(); // 🚨 race condition
}
return instance;
}
}3️⃣ Synchronized accessor (thread-safe but slow)
synchronized makes it correct, but every read now pays a lock cost.
public final class SyncSingleton {
private static SyncSingleton instance;
private SyncSingleton() {}
public static synchronized SyncSingleton getInstance() {
if (instance == null) instance = new SyncSingleton();
return instance;
}
}4️⃣ Double-Checked Locking (DCL) with volatile
Lock only on first creation. volatile is required so other threads never see a half-built instance.
public final class DclSingleton {
private static volatile DclSingleton instance;
private DclSingleton() {}
public static DclSingleton getInstance() {
DclSingleton local = instance;
if (local == null) {
synchronized (DclSingleton.class) {
local = instance;
if (local == null) {
local = new DclSingleton();
instance = local;
}
}
}
return local;
}
}volatile and you have a classic publication bug.5️⃣ Bill Pugh / Static Holder (recommended for lazy)
The inner class is not loaded until getInstance() is called — lazy + thread-safe with zero locking.
public final class HolderSingleton {
private HolderSingleton() {}
private static class Holder {
static final HolderSingleton INSTANCE = new HolderSingleton();
}
public static HolderSingleton getInstance() {
return Holder.INSTANCE;
}
}6️⃣ Enum Singleton (Joshua Bloch's recommendation)
A one-element enum is the simplest singleton in Java — and the only one immune to reflection and serialization attacks.
public enum EnumSingleton {
INSTANCE;
private final Map<String, String> cache = new ConcurrentHashMap<>();
public void put(String k, String v) { cache.put(k, v); }
public String get(String k) { return cache.get(k); }
}
// Usage: EnumSingleton.INSTANCE.put("env", "prod");Common Mistakes / Anti-Patterns
- Using Singleton as a global variable bucket — drop in unrelated state because "it's convenient".
- Mutable singletons accessed from many threads without synchronization → race conditions.
- Singletons that depend on other singletons → tangled init order, classic NPE on startup.
- Forgetting
volatilein DCL — one of the most famous broken patterns in Java history (the "DCL is broken" papers). - Allowing reflection to break it:
setAccessible(true)on the private constructor → second instance. - Forgetting to override
readResolve()on aSerializablesingleton → deserialization creates a new instance.
Interview Q&A
Likely Follow-up Questions
- Implement a thread-safe Singleton on the whiteboard. (Holder idiom or enum is the safest answer.)
- How would you write a Singleton that supports two configurations (e.g., test vs prod)? (You probably shouldn't — switch to DI.)
- How does Spring achieve singleton scope? (Bean factory caches instances per
ApplicationContext.) - Walk me through why the original double-checked locking was broken before Java 5. (Memory model didn't guarantee a happens-before relationship;
volatilesemantics changed in JSR-133.) - How would you make a Singleton lazily initialized and parameterized? (Holder idiom + builder, or "initialize-on-first-use" with explicit
init(config)— usually a smell.) - Name three real-world Singletons in the JDK or popular frameworks. (
Runtime.getRuntime(),Logger.getLogger("name")per name, Spring's default bean scope.)
Quick Cheat Sheet
volatile, hidden global state.getInstance() from deep in domain logic.