Thread-Safe Singleton in Java
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.
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());
}
}Instances created: 8Explanation: 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
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());
}
}true
true
true
Hello from the enum singletonExplanation:
- 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 intolocalreads the volatile field once on the fast path. - Holder idiom: the nested
Holderclass isn't loaded untilgetInstance()first touches it, and the JVM guarantees class initialization happens exactly once, safely. Lazy, fast and lock-free. - Enum: the JVM creates
INSTANCEonce, safely. It also resists reflection and serialization tricks that can create extra copies of other singletons.
Comparison
| Approach | Lazy | Lock on every call | Safe against serialization and reflection | Notes |
|---|---|---|---|---|
Eager static final field | No | No | No | Simplest if creation is cheap |
synchronized getInstance | Yes | Yes | No | Simple but slower |
Double-checked + volatile | Yes | No (only first time) | No | Easy to get wrong |
| Holder idiom | Yes | No | No | Clean and fast |
| Enum | No (created on first use of the enum) | No | Yes | Recommended 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
- Easy: Run
Brokenseveral times and note the instance counts. Then makegetInstancesynchronized and confirm the count is always 1. - Harder: Remove
volatilefromDclSingletonand explain, using happens-before, why the code is no longer guaranteed correct even though it seems to work.
Key Takeaways
Was this tutorial helpful?