Java 4 min read

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.

MR

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:

  1. First check - Quick check without synchronization (fast path for already-initialized case)
  2. Synchronized block - Only entered if instance might be null
  3. 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:

  1. The compiler/JIT can reorder instructions
  2. 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:

  1. The SingletonHolder class is not loaded until getInstance() is called - Classes in Java are loaded on first use
  2. Class loading is thread-safe - The JVM guarantees that class initialization is atomic
  3. 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

ApproachFirst AccessSubsequent AccessComplexity
Synchronized MethodSlowSlow (always locks)Simple
Double-Checked LockingMediumFastMedium
Bill PughFastFastSimple

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

ImplementationThread-SafeLazyPerformanceRecommended
EagerYesNoGoodSimple cases
SynchronizedYesYesPoorAvoid
Double-CheckedYesYesGoodLegacy code
Bill PughYesYesBestGeneral use
EnumYesNoGoodWhen 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

MR

Moshiour Rahman

Software Architect & AI Engineer

Share:
MR

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

Comments

Comments are powered by GitHub Discussions.

Configure Giscus at giscus.app to enable comments.