Thread-Safe Singleton in Java

Naveen AnamaneniUpdated · 5 min read

A singleton is a class that has exactly one instance (see "Singleton Pattern in Java"). When several threads ask for the instance at the same time, a careless singleton can create two instances, or hand out an object that isn't fully built. In this lesson, you will see the problem, then learn four thread-safe ways to write a singleton: a synchronized method, double-checked locking with volatile, the holder idiom, and the enum singleton, and when to use each.

The Problem: A Lazy Singleton Without Locking

Let's see an example where many threads ask a slow-to-build singleton for its instance at once. The output varies, but it usually shows several instances being created.

Broken.java▶ Try it
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
 
public class Broken {
    static class Config {
        private static Config instance;
 
        private Config() {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
 
        static Config getInstance() {
            if (instance == null) {
                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("Instances created: " + seen.size());
    }
}
Output
Instances created: 8

Explanation: All eight threads see instance == null before any of them finishes constructing, so each creates its own Config. That's a check-then-act race.

Four Thread-Safe Solutions

Singletons.java▶ Try it
public class Singletons {
    static class SyncSingleton {
        private static SyncSingleton instance;
        private SyncSingleton() { }
        static synchronized SyncSingleton getInstance() {
            if (instance == null) {
                instance = new SyncSingleton();
            }
            return instance;
        }
    }
 
    static class DclSingleton {
        private static volatile DclSingleton instance;
        private DclSingleton() { }
        static DclSingleton getInstance() {
            DclSingleton local = instance;
            if (local == null) {
                synchronized (DclSingleton.class) {
                    local = instance;
                    if (local == null) {
                        instance = local = new DclSingleton();
                    }
                }
            }
            return local;
        }
    }
 
    static class HolderSingleton {
        private HolderSingleton() { }
        private static class Holder {
            static final HolderSingleton INSTANCE = new HolderSingleton();
        }
        static HolderSingleton getInstance() {
            return Holder.INSTANCE;
        }
    }
 
    enum EnumSingleton {
        INSTANCE;
        String greet() { return "Hello from the enum singleton"; }
    }
 
    public static void main(String[] args) {
        System.out.println(SyncSingleton.getInstance()
            == SyncSingleton.getInstance());
        System.out.println(DclSingleton.getInstance()
            == DclSingleton.getInstance());
        System.out.println(HolderSingleton.getInstance()
            == HolderSingleton.getInstance());
        System.out.println(EnumSingleton.INSTANCE.greet());
    }
}
Output
true
true
true
Hello from the enum singleton

Explanation:

  • Synchronized method: simple and correct, but every call takes the class lock, even after the instance exists.
  • Double-checked locking: checks without a lock first, and locks only while creating. The field must be volatile; otherwise another thread could see the reference before the object's fields are set (see "Java Memory Model and Happens-Before"). Copying into local reads the volatile field once on the fast path.
  • Holder idiom: the nested Holder class isn't loaded until getInstance() first touches it, and the JVM guarantees class initialization happens exactly once, safely. Lazy, fast and lock-free.
  • Enum: the JVM creates INSTANCE once, safely. It also resists reflection and serialization tricks that can create extra copies of other singletons.

Comparison

ApproachLazyLock on every callSafe against serialization and reflectionNotes
Eager static final fieldNoNoNoSimplest if creation is cheap
synchronized getInstanceYesYesNoSimple but slower
Double-checked + volatileYesNo (only first time)NoEasy to get wrong
Holder idiomYesNoNoClean and fast
EnumNo (created on first use of the enum)NoYesRecommended by Joshua Bloch in Effective Java

Common Mistakes

Double-checked locking without volatile. Another thread may see a half-built object.

Making the constructor public. Anyone can create more instances.

Assuming a non-enum singleton survives serialization. Deserializing creates a new instance unless you add readResolve.

Interview Corner

How do you create a thread-safe singleton?

Options include eager initialization in a static final field; a synchronized getInstance (correct but locks every call); double-checked locking, where the instance field must be volatile so that the reference isn't published before construction completes; the initialization-on-demand holder idiom, which relies on the JVM's thread-safe, lazy class initialization; and a single-element enum, which is thread-safe and also protects against reflection and serialization creating extra instances. The holder idiom and enum are generally preferred.

Practice

  1. Easy: Run Broken several times and note the instance counts. Then make getInstance synchronized and confirm the count is always 1.
  2. Harder: Remove volatile from DclSingleton and explain, using happens-before, why the code is no longer guaranteed correct even though it seems to work.

Quick check: Thread-safe singleton

4 questions
1Why can a lazy singleton without locking create two instances?
2What must the field be in double-checked locking?
3Why is the holder idiom thread-safe?
4Which singleton also resists serialization and reflection attacks?

Key Takeaways

Was this tutorial helpful?

Lesson 410 of 482