🎯 Singleton Design Pattern
Ensure a class has only one instance and provide a global point of access to it.
Real-World Analogy
Think of the President of a country. There can only be one active president at a time. No matter who asks "Who is the president?", they all get the same answer — the same single instance. The office (class) ensures only one person (instance) holds the position at any given time.
%%{init: {'theme': 'base', 'themeVariables': {'fontSize': '13px', 'fontFamily': 'Inter, -apple-system, sans-serif'}, 'flowchart': {'nodeSpacing': 30, 'rankSpacing': 50, 'padding': 12, 'curve': 'basis'}, 'sequence': {'actorMargin': 60, 'messageMargin': 40}, 'class': {'padding': 12}}}%%
flowchart LR
A["👤 Citizen A"] -->|"asks"| P["🏛️ The President"]
B["👤 Citizen B"] -->|"asks"| P
C["👤 Citizen C"] -->|"asks"| P
P -->|"same answer"| R(["🎯 One Instance"])
style P fill:#FFF3E0,stroke:#E65100,stroke-width:2px,color:#000
style A fill:#E3F2FD,stroke:#1565C0,color:#000
style B fill:#E3F2FD,stroke:#1565C0,color:#000
style C fill:#E3F2FD,stroke:#1565C0,color:#000
style R fill:#FFF8E1,stroke:#F9A825,color:#000 🏗️ Structure
%%{init: {'theme': 'base', 'themeVariables': {'fontSize': '13px', 'fontFamily': 'Inter, -apple-system, sans-serif'}, 'flowchart': {'nodeSpacing': 30, 'rankSpacing': 50, 'padding': 12, 'curve': 'basis'}, 'sequence': {'actorMargin': 60, 'messageMargin': 40}, 'class': {'padding': 12}}}%%
flowchart LR
A["🟢 Client"] -->|"getInstance()"| B[["🎯 Singleton"]]
B -->|"returns"| C(["📦 Single Instance"])
D["🔒 Private Constructor"] -.->|"blocks new"| B
style A fill:#E8F5E9,stroke:#2E7D32,color:#000
style B fill:#FFF3E0,stroke:#E65100,stroke-width:2px,color:#000
style C fill:#FFF8E1,stroke:#F9A825,color:#000
style D fill:#FCE4EC,stroke:#C62828,color:#000 UML Class Diagram
%%{init: {'theme': 'base', 'themeVariables': {'fontSize': '13px', 'fontFamily': 'Inter, -apple-system, sans-serif'}, 'flowchart': {'nodeSpacing': 30, 'rankSpacing': 50, 'padding': 12, 'curve': 'basis'}, 'sequence': {'actorMargin': 60, 'messageMargin': 40}, 'class': {'padding': 12}}}%%
classDiagram
class Singleton {
-instance$ : Singleton
-Singleton()
+getInstance()$ Singleton
}
class Client {
}
Client ..> Singleton : calls getInstance()
style Singleton fill:#FFF3E0,stroke:#E65100,color:#000
style Client fill:#E3F2FD,stroke:#1565C0,color:#000 ❓ The Problem
In many applications, certain objects should exist only once:
- Database connection pools — creating multiple pools wastes resources
- Configuration managers — conflicting configs cause chaos
- Logger instances — multiple loggers writing to the same file corrupt data
- Cache managers — duplicate caches waste memory and cause inconsistency
- Thread pools — uncontrolled pool creation leads to resource exhaustion
Without the Singleton pattern, any code can create new instances freely, leading to resource waste, inconsistent state, and hard-to-debug issues.
Without This Pattern
// Anyone can create new instances — no control
public class DatabaseConnectionPool {
private List<Connection> connections = new ArrayList<>();
public DatabaseConnectionPool(int size) {
for (int i = 0; i < size; i++) {
connections.add(createConnection()); // Expensive!
}
}
public Connection getConnection() {
return connections.remove(0);
}
}
// In ServiceA.java
DatabaseConnectionPool poolA = new DatabaseConnectionPool(10);
// In ServiceB.java — OOPS, another pool!
DatabaseConnectionPool poolB = new DatabaseConnectionPool(10);
// In ServiceC.java — yet another!
DatabaseConnectionPool poolC = new DatabaseConnectionPool(10);
// Now 30 connections open instead of 10. DB runs out of connections.
Problems:
- Resource exhaustion — each
newcreates an independent pool, multiplying DB connections beyond limits - Inconsistent state — one pool may be drained while others sit idle; no shared view of available connections
- No single point of control — impossible to enforce connection limits, timeouts, or monitoring across multiple pools
- Violates DRY — initialization logic (pool size, config) is duplicated and can drift out of sync
- Hard to debug — when the DB rejects connections, which of the N pools is the culprit?
✅ The Solution
The Singleton pattern solves this by:
- Making the constructor private — prevents external instantiation
- Providing a static method as the single access point
- Storing the instance in a private static field
- Returning the same instance every time the method is called
🛠️ Implementation
The simplest approach — instance created at class loading time.
public class Singleton {
// Instance created at class loading — thread-safe by JVM guarantee
private static final Singleton INSTANCE = new Singleton();
private Singleton() {
// Prevent reflection-based instantiation
if (INSTANCE != null) {
throw new IllegalStateException("Instance already created!");
}
}
public static Singleton getInstance() {
return INSTANCE;
}
}
Pros & Cons
Pros: Simple, inherently thread-safe, no synchronization overhead
Cons: Instance created even if never used (wastes memory if object is heavy)
Instance created only when first requested — but NOT thread-safe.
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton(); // Race condition possible!
}
return instance;
}
}
Not Thread-Safe
Two threads can simultaneously see instance == null and create two objects. Never use this in multi-threaded environments.
Solves the thread-safety issue but with a performance cost.
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
Pros & Cons
Pros: Thread-safe
Cons: synchronized on every call — even after instance is created. Causes unnecessary lock contention.
The industry-standard approach — minimal synchronization overhead.
public class Singleton {
// volatile prevents instruction reordering
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) { // First check (no lock)
synchronized (Singleton.class) {
if (instance == null) { // Second check (with lock)
instance = new Singleton();
}
}
}
return instance;
}
}
Why volatile?
Without volatile, the JVM may reorder instructions: allocate memory → assign reference → call constructor. Another thread could see a non-null but partially constructed object. volatile prevents this reordering.
The most elegant lazy-loading solution — leverages JVM class loading.
public class Singleton {
private Singleton() {}
// Inner class is not loaded until getInstance() is called
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
Why This Works
The JVM guarantees that a class is loaded only when it's first referenced. SingletonHolder is loaded only when getInstance() is called — giving us lazy initialization with zero synchronization cost.
The best approach — recommended by Joshua Bloch in Effective Java.
public enum Singleton {
INSTANCE;
// Add your methods here
private int counter = 0;
public int getCounter() {
return counter;
}
public void increment() {
counter++;
}
}
// Usage
// Singleton.INSTANCE.increment();
// int count = Singleton.INSTANCE.getCounter();
Why Enum is Best
- Thread-safe by JVM guarantee
- Serialization-safe — JVM handles it natively
- Reflection-safe — JVM prevents instantiation of enum via reflection
- Concise — no boilerplate code
🎯 When to Use
- When exactly one instance of a class is needed across the application
- When you need controlled access to a shared resource (DB pool, cache, config)
- When the single instance should be extensible by subclassing (use registry-based approach)
- When you need a global access point but want to avoid global variables
🌍 Real-World Examples
| Framework / Library | Singleton Usage |
|---|---|
java.lang.Runtime | Runtime.getRuntime() |
java.lang.System | System class with static fields |
| Spring Framework | Default bean scope is singleton |
java.awt.Desktop | Desktop.getDesktop() |
| SLF4J / Log4j | LoggerFactory.getLogger() |
| Hibernate | SessionFactory (one per DB) |
Pitfalls
- Broken by Reflection — Use enum or throw exception in constructor
- Broken by Serialization — Implement
readResolve()or use enum - Broken by Cloning — Override
clone()to throwCloneNotSupportedException - Hidden Dependencies — Classes using Singleton are tightly coupled to it
- Testing Difficulty — Hard to mock; prefer dependency injection in production code
- Classloader Issues — Multiple classloaders can create multiple instances
- Violates Single Responsibility — The class manages its own lifecycle AND business logic
Key Takeaways
| Approach | Lazy? | Thread-Safe? | Reflection-Safe? | Serialization-Safe? |
|---|---|---|---|---|
| Eager | No | Yes | No | No |
| Lazy | Yes | No | No | No |
| Synchronized | Yes | Yes | No | No |
| Double-Checked | Yes | Yes | No | No |
| Bill Pugh | Yes | Yes | No | No |
| Enum | No | Yes | Yes | Yes |
- For interviews: Know all approaches and their trade-offs
- For production: Use Enum Singleton or Spring's DI container
- For legacy code: Double-Checked Locking with
volatileis acceptable