Java Programs›Multithreading›Implement a Thread-Safe Singleton

Implement a Thread-Safe Singleton in Java

advanced·  Multithreading  ·  Concurrency

Problem

The singleton pattern ensures a class has only one instance; with multiple threads, two of them can see "no instance yet" at the same moment and each create one, unless creation is synchronised.

Implement a lazily created singleton and show that eight threads requesting it at the same time all receive the same single instance.

Input
8 threads call getInstance()
Output
constructor runs once, 1 distinct instance

Java Program

Java
import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; public class ThreadSafeSingleton { static class Config { static final AtomicInteger CREATED = new AtomicInteger(); private static volatile Config instance; // volatile: other threads see a fully built object private Config() { CREATED.incrementAndGet(); } static Config getInstance() { if (instance == null) { // first check, without locking synchronized (Config.class) { if (instance == null) { // second check, now holding the lock instance = new Config(); } } } return instance; } } public static void main(String[] args) throws InterruptedException { Set<Config> seen = ConcurrentHashMap.newKeySet(); Thread[] threads = new Thread[8]; for (int i = 0; i < threads.length; i++) { threads[i] = new Thread(() -> seen.add(Config.getInstance())); threads[i].start(); } for (Thread t : threads) t.join(); System.out.println("Threads asking for the instance: " + threads.length); System.out.println("Constructor calls: " + Config.CREATED.get()); System.out.println("Distinct instances seen: " + seen.size()); } }

Output

Threads asking for the instance: 8 Constructor calls: 1 Distinct instances seen: 1

Core Logic

Double-checked locking checks for an instance without a lock first, and takes the lock only when one might need to be created — so the common case stays fast.

How It Works
  1. 1The constructor is private, so only getInstance() can create the object.
  2. 2The first if (instance == null) skips locking entirely once the instance exists.
  3. 3Inside synchronized (Config.class), the second check stops a thread that waited for the lock from creating a second instance.
  4. 4volatile on the field guarantees other threads never see a partly constructed object.
All eight threads add the reference they received to a set; the set ends up with one element, and the constructor counter reads 1.
💡

Key Point: Without volatile, double-checked locking is broken: the reference can be published before the constructor has finished, and another thread may use a half-initialised object.

Key Concepts

double-checked lockingvolatilesynchronizedprivate constructor

Approach 2: Holder Idiom and Enum Singleton

Java
public class SingletonHolderIdiom { static class Registry { private Registry() { System.out.println("Registry created"); } private static class Holder { // not loaded until getInstance() first touches it static final Registry INSTANCE = new Registry(); } static Registry getInstance() { return Holder.INSTANCE; } } enum AppSettings { // the enum singleton: one instance, guaranteed by the JVM INSTANCE; String theme() { return "dark"; } } public static void main(String[] args) { System.out.println("Before first use"); Registry a = Registry.getInstance(); Registry b = Registry.getInstance(); System.out.println("Same Registry: " + (a == b)); System.out.println("Enum singleton theme: " + AppSettings.INSTANCE.theme()); } }

Output

Before first use Registry created Same Registry: true Enum singleton theme: dark

Core Logic

Two simpler alternatives let the JVM handle thread safety: a static holder class that loads on first use, and a single-element enum.

How It Works
  1. 1Holder.INSTANCE is created when the nested Holder class is first loaded, which happens inside the first getInstance() call.
  2. 2Class initialisation is thread-safe by specification, so no synchronized or volatile is needed.
  3. 3The message "Registry created" prints after "Before first use", proving creation is lazy.
  4. 4enum AppSettings { INSTANCE; } is a singleton that also survives serialisation and reflection attacks.
Two calls to getInstance() return the same object, so a == b is true.
💡

Key Point: The enum form is the approach recommended in Effective Java; use the holder idiom when the singleton must extend a class or be created lazily.

Key Concepts

static nested classclass loadingenum singleton

Interview Questions

Can reflection break a singleton?

Yes. Reflection can call a private constructor unless the constructor throws when an instance already exists. The enum singleton is immune to this.

How does serialisation affect a singleton?

Deserialising creates a new object unless the class defines readResolve() returning the existing instance. Enum singletons handle this automatically.

Learn the Concept

Related Programs