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
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.
JavaUnderstanding Final Classes in Java
Learn when and why to use final classes in Java. Understand the design principle of prohibiting inheritance for better code safety.
JavaLombok: Reduce Java Boilerplate Code
Complete guide to Project Lombok for Java. Learn how to eliminate boilerplate code with annotations like @Data, @Builder, and @RequiredArgsConstructor.
Comments
Comments are powered by GitHub Discussions.
Configure Giscus at giscus.app to enable comments.