How many ways can you write a singleton in Java, and what are the trade-offs?
There are four implementations worth knowing, and they differ in when the instance is created and how thread safety is achieved.
Eager creates the instance in a static final field at class initialisation time. It is the simplest and it is thread safe, because the JVM guarantees class initialisation happens exactly once. The cost is that the instance exists even if nobody ever asks for it, so if the constructor is expensive or has side effects you pay for it whether you need it or not.
Lazy holder declares the instance inside a private static nested class and returns it from the outer class. The nested class is initialised the first time it is touched, so the instance is created on first use — and it is thread safe, because the JVM guarantees the nested class is initialised exactly once. This is the idiom most Java libraries use.
Enum declares the singleton as an enum constant. It is thread safe, it is serialisable in a way that cannot be broken by deserialisation, and it cannot be subclassed. It is the most robust of the four, and the one to reach for if you want a singleton without ceremony.
Double-checked locking uses a volatile field and a synchronised block around the creation. It is the hardest to get right, and the version people used to write before the memory model was fixed had a subtle bug where a partially constructed object could be observed. If you remember one thing about it, it is that the field must be volatile.
Here is the same thing as runnable code:
public class Main {
public static void main(String[] args) {
System.out.println("eager : " + EagerConfig.INSTANCE.getName());
System.out.println("holder : " + HolderConfig.getInstance().getName());
System.out.println("enum : " + EnumConfig.INSTANCE.getName());
System.out.println("same eager instance : " + (EagerConfig.INSTANCE == EagerConfig.INSTANCE));
System.out.println("same enum instance : " + (EnumConfig.INSTANCE == EnumConfig.INSTANCE));
}
}
class EagerConfig {
static final EagerConfig INSTANCE = new EagerConfig();
private final String name = "eagerly created";
private EagerConfig() {
}
String getName() {
return name;
}
}
class HolderConfig {
// Created when the holder class is first touched.
private static class Holder {
static final HolderConfig INSTANCE = new HolderConfig();
}
private final String name = "created on first use";
private HolderConfig() {
}
static HolderConfig getInstance() {
return Holder.INSTANCE;
}
String getName() {
return name;
}
}
enum EnumConfig {
INSTANCE;
private final String name = "an enum singleton";
String getName() {
return name;
}
}