Singleton Design Pattern in Java: Part 2
Advanced singleton implementations including Double-Checked Locking with volatile and the Bill Pugh pattern using static inner classes.
Moshiour Rahman
Advertisement
Recap from Part 1
In Part 1, we covered:
- The concept of the Singleton design pattern
- Eager initialization approaches
- Basic lazy loading (not thread-safe)
- Synchronized method (thread-safe but slow)
Now let’s explore more efficient thread-safe implementations!
Double-Checked Locking Singleton
This pattern is explained in the Wikipedia article on Double-checked locking.
public class DoubleCheckedLockingSingleton {
private static volatile DoubleCheckedLockingSingleton instance;
private DoubleCheckedLockingSingleton() {}
public static DoubleCheckedLockingSingleton getInstance() {
if (instance == null) { // First check (no locking)
synchronized (DoubleCheckedLockingSingleton.class) {
if (instance == null) { // Second check (with locking)
instance = new DoubleCheckedLockingSingleton();
}
}
}
return instance;
}
}
How It Works
The logic can be confusing. Here is the flow:

Instead of synchronizing the entire method, we only synchronize the instantiation block:
- First check - Quick check without synchronization (fast path for already-initialized case)
- Synchronized block - Only entered if instance might be null
- Second check - Prevents multiple instantiations when two threads pass the first check simultaneously
Why Two Null Checks?
Consider this scenario:
Thread T1: Passes first null check
Thread T2: Passes first null check
Thread T1: Enters synchronized block, creates instance
Thread T2: Enters synchronized block...
→ Without second check, T2 would create another instance!
The Critical Role of volatile
Without volatile, this pattern is broken!
Here’s why:
- The compiler/JIT can reorder instructions
- Object construction involves multiple steps:
- Allocate memory
- Initialize fields
- Assign reference to variable
Without volatile, the reference might be assigned before initialization completes:
Thread T1: Allocates memory, assigns to instance (partially constructed!)
Thread T2: Sees instance != null, returns partially constructed object
→ CRASH or undefined behavior!
The volatile keyword prevents this reordering, ensuring the object is fully constructed before the reference is visible to other threads.
For more details, see The “Double-Checked Locking is Broken” Declaration.
Bill Pugh Singleton (Initialization-on-demand Holder)
Bill Pugh’s solution is the most elegant and efficient approach. It uses a static inner class to achieve lazy initialization.
public class BillPughSingleton {
private BillPughSingleton() {}
private static class SingletonHolder {
private static final BillPughSingleton INSTANCE = new BillPughSingleton();
}
public static BillPughSingleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
Why This Works
This pattern leverages the JVM’s class loading mechanism:
- The
SingletonHolderclass is not loaded untilgetInstance()is called - Classes in Java are loaded on first use - Class loading is thread-safe - The JVM guarantees that class initialization is atomic
- No synchronization overhead - Subsequent calls don’t require any locking
Benefits
- Safe: Thread-safe without explicit synchronization
- Efficient: No synchronization overhead on reads
- Lazy: Instance created only when first accessed
- Simple: No
volatile, no synchronized blocks
This is considered the best approach for implementing singletons in Java.
Performance Comparison
| Approach | First Access | Subsequent Access | Complexity |
|---|---|---|---|
| Synchronized Method | Slow | Slow (always locks) | Simple |
| Double-Checked Locking | Medium | Fast | Medium |
| Bill Pugh | Fast | Fast | Simple |
Enum Singleton (Bonus)
Joshua Bloch recommends using an enum for singletons:
public enum EnumSingleton {
INSTANCE;
public void doSomething() {
System.out.println("Singleton operation");
}
}
Advantages:
- Thread-safe by default
- Serialization handled automatically
- Reflection-proof
- Simplest implementation
Disadvantage:
- Cannot extend other classes (enums implicitly extend
Enum)
Summary
| Implementation | Thread-Safe | Lazy | Performance | Recommended |
|---|---|---|---|---|
| Eager | Yes | No | Good | Simple cases |
| Synchronized | Yes | Yes | Poor | Avoid |
| Double-Checked | Yes | Yes | Good | Legacy code |
| Bill Pugh | Yes | Yes | Best | General use |
| Enum | Yes | No | Good | When possible |
Conclusion
This two-part series covered the Singleton design pattern comprehensively:
- What is the singleton pattern
- Where it’s used (Spring, Runtime, etc.)
- When to use it
- How to implement it correctly
Best Practices:
- Use the Bill Pugh pattern for most cases
- Consider enums when you don’t need lazy initialization
- Avoid the synchronized method approach
- Always make the constructor private
- Be careful with serialization (can break singleton)
Thanks for reading! See you in the next post.
Advertisement
Moshiour Rahman
Software Architect & AI Engineer
Enterprise software architect with deep expertise in financial systems, distributed architecture, and AI-powered applications. Building large-scale systems at Fortune 500 companies. Specializing in LLM orchestration, multi-agent systems, and cloud-native solutions. I share battle-tested patterns from real enterprise projects.
Related Articles
Singleton Design Pattern in Java: Part 1
Deep dive into the Singleton design pattern in Java. Learn different implementation approaches including eager initialization and lazy loading.
JavaThe Visitor Design Pattern: Add Operations Without Modifying Classes
Master the Visitor Pattern in Java. Learn how to add new operations to object structures using double dispatch.
JavaThe Template Method Pattern: The Recipe for Success
Master the Template Method Pattern in Java. Learn how to define the skeleton of an algorithm in a superclass but let subclasses override specific steps.
Comments
Comments are powered by GitHub Discussions.
Configure Giscus at giscus.app to enable comments.