Implement a Thread-Safe Singleton in Java
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.
Java Program
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
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.
- 1The constructor is
private, so onlygetInstance()can create the object. - 2The first
if (instance == null)skips locking entirely once the instance exists. - 3Inside
synchronized (Config.class), the second check stops a thread that waited for the lock from creating a second instance. - 4
volatileon the field guarantees other threads never see a partly constructed object.
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
Approach 2: Holder Idiom and Enum Singleton
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
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.
- 1
Holder.INSTANCEis created when the nestedHolderclass is first loaded, which happens inside the firstgetInstance()call. - 2Class initialisation is thread-safe by specification, so no
synchronizedorvolatileis needed. - 3The message "Registry created" prints after "Before first use", proving creation is lazy.
- 4
enum AppSettings { INSTANCE; }is a singleton that also survives serialisation and reflection attacks.
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
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.