Question 4 · Section 2

How to Implement Thread-Safe Singleton?

If two threads simultaneously check instance == null, both will create their own object — resulting in two instances instead of one. To make Singleton work in a multithreaded en...

Language versions: English Russian Ukrainian

Junior Level

If two threads simultaneously check instance == null, both will create their own object — resulting in two instances instead of one. To make Singleton work in a multithreaded environment, you need to protect instance creation.

The simple problem:

// Two threads can simultaneously create two instances!
if (instance == null) {
    instance = new Singleton();  // Thread 1 and Thread 2 at the same time!
}

The simplest approach — Enum:

public enum Singleton {
    INSTANCE;  // JVM guarantees a single instance

    public void doWork() {
        System.out.println("Working...");
    }
}

// Usage
Singleton.INSTANCE.doWork();
// Thread-safe, protected from reflection and serialization

Why Enum is safe:

  • Java forbids creating enums via new
  • Serialization works correctly
  • Concurrency is provided by JVM

When NOT to implement thread-safe Singleton manually

In Spring applications: @Component + singleton scope (default) already manages this. In CDI: @ApplicationScoped. Don’t reinvent the wheel.


Middle Level

public enum Singleton {
    INSTANCE;

    private final String config;

    Singleton() {
        this.config = loadConfig();
    }

    public String getConfig() {
        return config;
    }
}

Advantages:

  • Thread-safe (JVM)
  • Protected from reflection
  • Protected from serialization
  • Simple code

Disadvantages:

  • Not lazy (created on class loading)

Approach 2: Bill Pugh (Static Holder)

public class BillPughSingleton {
    private BillPughSingleton() {}

    // Inner class loads only on first access
    private static class SingletonHolder {
        private static final BillPughSingleton INSTANCE = new BillPughSingleton();
    }

    public static BillPughSingleton getInstance() {
        return SingletonHolder.INSTANCE;
    }

    // Serialization protection
    protected Object readResolve() {
        return getInstance();
    }
}

Why it works:

  • JVM guarantees atomicity of static field initialization
  • SingletonHolder class loads only on first getInstance() call
  • Lazy initialization without explicit synchronized — thread-safety ensured by ClassLoader locking (JLS 12.4.2).

Approach 3: Double-Checked Locking

public class DclSingleton {
    private static volatile DclSingleton instance;

    private DclSingleton() {
        if (instance != null) {
            throw new IllegalStateException("Already created");
        }
    }

    public static DclSingleton getInstance() {
        DclSingleton local = instance;  // Optimization
        if (local == null) {
            synchronized (DclSingleton.class) {
                local = instance;
                if (local == null) {
                    instance = local = new DclSingleton();
                }
            }
        }
        return local;
    }
}

Why volatile is needed:

  • Without volatile, reordering is possible
  • A thread may get a reference to an UNINITIALIZED object
  • volatile prevents reordering

Before Java 5 (JSR-133), the Memory Model did not guarantee happens-before for volatile, and DCL was broken.

Comparison

Approach Lazy Thread-Safety Complexity
Enum No Full Minimal
Bill Pugh Yes Full Low
DCL + volatile Yes Full High

In Spring

// NO need to write Singleton manually!
@Component  // Singleton scope by default
public class UserService { }

// Spring itself guarantees a single instance
// + testability through DI

Senior Level

Java Memory Model Deep Dive

Problem without volatile:

// instance = new Singleton() consists of 3 steps:
// 1. Allocate memory
// 2. Invoke constructor
// 3. Assign reference to instance

// Without volatile, reorder is possible: 1 -> 3 -> 2
// Thread B sees instance != null at step 3
// But constructor (step 2) is not yet executed!
// -> Thread B gets an object with default fields!

Volatile solves it:

// volatile inserts Memory Barriers:
// StoreStore before writing to volatile
// LoadLoad after reading from volatile

// This guarantees:
// All writes in constructor are completed BEFORE reference is published

Local Variable Optimization:

public static DclSingleton getInstance() {
    DclSingleton local = instance;  // Read volatile ONCE
    if (local == null) {
        synchronized (DclSingleton.class) {
            local = instance;
            if (local == null) {
                instance = local = new DclSingleton();
            }
        }
    }
    return local;  // Use local variable
}

// Without optimization: 2 volatile reads (slow)
// With optimization: 1 volatile read (~25% faster)

VarHandle (Java 9+)

// For extreme performance
public class VarHandleSingleton {
    private static final VarHandle HANDLE = MethodHandles.lookup()
        .findStaticVarHandle(VarHandleSingleton.class, "instance", VarHandleSingleton.class);

    private static VarHandleSingleton instance;

    public static VarHandleSingleton getInstance() {
        VarHandleSingleton local = (VarHandleSingleton) HANDLE.getAcquire();
        if (local == null) {
            synchronized (VarHandleSingleton.class) {
                local = (VarHandleSingleton) HANDLE.getAcquire();
                if (local == null) {
                    local = new VarHandleSingleton();
                    HANDLE.setRelease(null, local);
                }
            }
        }
        return local;
    }
}

// getAcquire/setRelease are cheaper than volatile
// But sufficient for DCL semantics

Initialization-on-demand Holder: Internal Mechanics

// JVM Class Loading mechanism:
// 1. Class loads on first access
// 2. Static field initialization = atomic
// 3. ClassLoader internal locking guarantees thread-safety

// This is NOT "magic" — it's JLS (Java Language Specification) 12.4.1:
// "Initialization of a class consists of executing its static initializers"
// "The Java programming language implements multiple-threaded initialization correctly"

Protection from All Attacks

public class SecureSingleton implements Serializable {
    private static final SecureSingleton INSTANCE = new SecureSingleton();

    private SecureSingleton() {
        if (INSTANCE != null) {
            throw new IllegalStateException("Singleton violation");
        }
    }

    public static SecureSingleton getInstance() {
        return INSTANCE;
    }

    // Serialization protection
    private Object readResolve() throws ObjectStreamException {
        return INSTANCE;
    }

    // Cloning protection
    @Override
    protected Object clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException("Singleton cannot be cloned");
    }
}

Performance Comparison

Benchmark (10M calls):
  Enum:              10ms  (static final, JIT optimizes)
  Bill Pugh:         12ms  (class loading once)
  DCL + volatile:    15ms  (volatile read overhead)
  VarHandle:         11ms  (weaker barriers)
  synchronized:      150ms (lock overhead every time!)

Production Experience

Real scenario: DCL without volatile in production

  • Application worked for 2 years without issues
  • After migrating to new JDK → random NPEs
  • Cause: JDK changed JIT optimization strategy
  • Solution: added volatile → problem disappeared

Best Practices

  1. Enum — for 99% of cases (safety > laziness)
  2. Bill Pugh — when laziness is critical
  3. DI Container — in Spring applications (DON’T write manually!)
  4. volatile is MANDATORY in DCL
  5. Local variable optimization in DCL
  6. readResolve() for serialization protection
  7. Constructor check for reflection protection
  8. VarHandle (Java 9+) for extreme performance

Senior Summary

  • JMM: volatile prevents reordering through Memory Barriers
  • Bill Pugh: relies on JLS 12.4.1 guarantees
  • Enum: native protection from reflection/serialization
  • Local variable: reduces volatile reads by 25%
  • VarHandle: weaker barriers -> faster than volatile
  • Spring: DON’T write Singleton — use @Component
  • DCL without volatile = a ticking time bomb

Interview Cheat Sheet

Must know:

  • Enum Singleton — recommended approach: thread-safe, protected from reflection and serialization
  • Bill Pugh (Static Holder) — lazy initialization without synchronized, via static inner class (JLS 12.4.1)
  • Double-Checked Locking requires volatile to prevent instruction reordering
  • Local variable optimization in DCL reduces volatile reads by 25%
  • In Spring applications DON’T write Singleton manually — @Component with singleton scope
  • VarHandle (Java 9+) provides getAcquire/setRelease — cheaper than volatile
  • Without volatile: reordering 1->3->2 (memory -> publish -> constructor) -> uninitialized object

Common follow-up questions:

  • Why is volatile mandatory in DCL? — Without it, JIT/CPU may reorder: allocate memory -> publish reference -> call constructor
  • How does Bill Pugh work? — Nested class loads only on first access, JLS guarantees atomic initialization
  • Enum vs Bill Pugh — which to choose? — Enum if laziness is not needed, Bill Pugh if laziness is critical
  • Why doesn’t Spring require manual Singleton? — @Component with singleton scope, + testability through DI

Red flags (DO NOT say):

  • “In Spring I write Singleton manually via synchronized” — @Component already manages this
  • “Volatile is not needed, it works for me” — reordering appears only on specific CPU/JDK
  • “DCL without volatile is safe” — undefined behavior, especially on ARM processors
  • “Enum doesn’t fit Singleton” — this is the Joshua Bloch recommended approach

Related topics:

  • [[03. What is Singleton]] — general pattern description
  • [[05. What is double-checked locking]] — DCL details
  • [[06. What are problems with Singleton]] — Singleton problems
  • [[02. What pattern categories exist]] — Creational patterns
  • [[16. What anti-patterns do you know]] — when Singleton becomes an anti-pattern