Back to ThreadLocal & Thread Confinement
Q6Question 6 of 6

Thread-safe Singleton implementations?

Key Concepts
  • Eager: private static final INSTANCE created at class load; thread-safe via JVM class-init guarantee.
  • Synchronized getInstance: lazy and correct but every call pays the lock cost, a bottleneck under concurrency.
  • Double-checked locking: volatile field, null check without lock, then lock and re-check; fast after first creation.
  • Bill Pugh holder: private static inner class holds the instance; lazy, thread-safe, no explicit synchronization.
  • Enum singleton: simplest and most robust; immune to serialization and reflection attacks.
  • Preferred: Bill Pugh for lazy loading, enum for the simplest bulletproof version.
  • If using double-checked locking, the instance field MUST be volatile to prevent seeing a partially constructed object.

Definition

For a thread-safe Singleton, prefer the Bill Pugh holder idiom for lazy loading or the enum for the simplest bulletproof version.
  • Eager – private static final INSTANCE = new X(): created at class load, thread-safe for free.
  • Synchronized getInstance – lazy and correct, but every call acquires the lock.
  • Double-checked locking – volatile field, null check outside lock, re-check inside; fast after creation.
  • Bill Pugh holder – private static inner class not loaded until first use; lazy, lock-free.
  • Enum singleton – single instance guaranteed by JVM; serialization-safe, reflection-safe.

National Bank of India has exactly one central vault. No matter how many tellers rush in at the same instant on opening day, there must be only one vault, and it must be fully built before anyone uses it.

Understanding the Concepts

What is eager initialization?

The simplest approach creates the instance as a static final field. The JVM guarantees that class initialization is thread-safe, so no synchronization is needed.

JAVA
package com.bank.pool;

public class ConnectionPool {
    private static final ConnectionPool INSTANCE = new ConnectionPool();
    private ConnectionPool() {}
    public static ConnectionPool getInstance() { return INSTANCE; }
}

Thread-safe because the JVM's class loading mechanism ensures only one thread initializes a class. Downside: the instance is created even if never used, which matters if construction is expensive.

What is synchronized getInstance?

JAVA
package com.bank.pool;

public class ConnectionPool {
    private static ConnectionPool instance;
    private ConnectionPool() {}
    public static synchronized ConnectionPool getInstance() {
        if (instance == null) instance = new ConnectionPool();
        return instance;
    }
}

Lazy and correct, but every call acquires the lock. Under high concurrency at National Bank of India, this becomes a bottleneck because threads serialize at the synchronized method even though the instance was created long ago.

What is double-checked locking?

Double-checked locking avoids the lock on every call by checking null outside the synchronized block first.

JAVA
package com.bank.pool;

public class ConnectionPool {
    private static volatile ConnectionPool instance;  // MUST be volatile
    private ConnectionPool() {}
    public static ConnectionPool getInstance() {
        if (instance == null) {                       // first check (no lock)
            synchronized (ConnectionPool.class) {
                if (instance == null) {               // second check (locked)
                    instance = new ConnectionPool();
                }
            }
        }
        return instance;
    }
}

The volatile keyword is mandatory. Without it, another thread could see a non-null reference to an object whose constructor has not finished executing. This is because instance = new ConnectionPool() is not atomic: it involves (1) allocating memory, (2) calling the constructor, and (3) assigning the reference. The JVM may reorder steps 2 and 3, so another thread sees a non-null reference to an uninitialized object.

What is the Bill Pugh holder idiom?

JAVA
package com.bank.pool;

public class ConnectionPool {
    private ConnectionPool() {}
    private static class Holder {
        static final ConnectionPool INSTANCE = new ConnectionPool();
    }
    public static ConnectionPool getInstance() { return Holder.INSTANCE; }
}

The inner class Holder is not loaded until getInstance() references it, so initialization is lazy. The JVM's class-init lock makes it thread-safe. No volatile, no synchronized, and no performance penalty after initialization.

What is the enum singleton?

JAVA
package com.bank.pool;

public enum ConnectionPool {
    INSTANCE;

    private final java.util.List<java.sql.Connection> connections = new java.util.ArrayList<>();

    public java.sql.Connection getConnection() {
        // pool logic
        return null;
    }
}

Simplest and most robust. The JVM guarantees a single instance. It is immune to serialization attacks (deserialization returns the same instance) and reflection attacks (the JVM prevents reflective construction of enum instances).

How do they compare?

| Approach | Lazy | Thread-safe | Serialization-safe | Reflection-safe | Performance |
|----------|------|-------------|-------------------|-----------------|-------------|
| Eager | No | Yes | Needs readResolve | No | Best |
| Synchronized | Yes | Yes | Needs readResolve | No | Worst |
| Double-checked | Yes | Yes (if volatile) | Needs readResolve | No | Good |
| Bill Pugh | Yes | Yes | Needs readResolve | No | Best |
| Enum | No | Yes | Yes | Yes | Best |

How can reflection break a non-enum Singleton?

JAVA
// Reflection attack on a non-enum Singleton
Constructor<ConnectionPool> ctor = ConnectionPool.class.getDeclaredConstructor();
ctor.setAccessible(true);
ConnectionPool second = ctor.newInstance();  // creates a second instance!
// second != ConnectionPool.getInstance()  -- Singleton broken!

// Defense: throw in the private constructor
private ConnectionPool() {
    if (INSTANCE != null) throw new IllegalStateException("Singleton already created");
}

Enum singletons are immune because the JVM throws IllegalArgumentException when you try to reflectively construct an enum.

What is readResolve for serialization safety?

When a Singleton is serialized and deserialized, Java creates a new object. The readResolve method tells the serialization framework to replace the deserialized object with the existing Singleton.

JAVA
private Object readResolve() {
    return INSTANCE;  // discard the deserialized object
}

What are common interview follow-up questions?

  • Is the Singleton pattern still relevant with dependency injection? Yes, DI containers manage the lifecycle (typically singleton scope), but understanding the pattern matters for non-DI contexts and interviews.
  • Why was double-checked locking broken before Java 5? Before Java 5, the JMM did not guarantee that volatile would prevent the reordering issue. The new JMM in Java 5 fixed this.
  • How do you test a Singleton? Inject the instance or use an interface so tests can provide a mock implementation.

Visual Overview

DIAGRAM
  Singleton Approaches:

  Eager:           class load ──▶ INSTANCE created ──▶ always available
  Synchronized:    first call ──▶ lock ──▶ create ──▶ every call locks
  Double-checked:  first call ──▶ null? ──▶ lock ──▶ null? ──▶ create
                   later calls ──▶ not null ──▶ return (no lock)
  Bill Pugh:       first call ──▶ loads Holder class ──▶ JVM init lock
                   later calls ──▶ Holder already loaded ──▶ return
  Enum:            class load ──▶ INSTANCE enum constant ──▶ bulletproof

Key Takeaway

A thread-safe Singleton must guarantee that only one instance is ever created and all threads see a fully constructed object. The five approaches are eager, synchronized, double-checked locking, Bill Pugh holder, and enum.

I prefer Bill Pugh for lazy loading because it is lazy, fast, and requires no explicit synchronization. For the simplest bulletproof version I use enum, which is also immune to serialization and reflection attacks.

At National Bank of India we use Bill Pugh for the connection pool Singleton and enum for a cached configuration object that gets serialized.

Remember one sentence: prefer the Bill Pugh holder for lazy loading or the enum for the simplest bulletproof Singleton, and if you ever use double-checked locking, the instance field must be volatile.

---

Share
Use ← → arrow keys to navigate