79. Singleton Pattern
The Singleton pattern ensures a class has exactly one instance; the enum singleton is the safest Java implementation โ it's thread-safe, serialization-safe, and immune to reflection attacks.
Java 21 (core language)Explanation
Three modern implementations ranked best-to-worst:
1. Enum singleton (best): Java enums are inherently singletons. Serialisation is handled correctly (enum constants are preserved). Reflection cannot create new instances. Zero boilerplate. The only downside: cannot extend a class (can implement interfaces).
2. Initialization-on-demand holder idiom: a static nested class holds the instance; the class is only loaded when first accessed, providing lazy initialization and thread safety via the Java class-loading guarantee.
3. Double-checked locking: requires the volatile keyword (for visibility) and is verbose. Prefer the holder idiom instead.
Common pitfalls: eager initialization (instance created at class load even if never used), non-volatile double-checked locking (broken on pre-Java-5 memory models), failure to protect against reflection (use AccessibleObject) or serialization (implement readResolve()).
Example
import java.io.Serializable;
public class SingletonPatterns {
// BEST: Enum singleton โ serialization-safe, reflection-safe, thread-safe
enum AppConfig {
INSTANCE;
private String dbUrl = "jdbc:postgresql://localhost/mydb";
public String getDbUrl() { return dbUrl; }
public void setDbUrl(String url) { this.dbUrl = url; }
public static AppConfig getInstance() { return INSTANCE; }
}
// GOOD: Initialization-on-demand holder (lazy, thread-safe)
static class MetricsRegistry {
private MetricsRegistry() {}
private static final class Holder {
static final MetricsRegistry INSTANCE = new MetricsRegistry();
}
public static MetricsRegistry getInstance() { return Holder.INSTANCE; }
public void record(String metric, double value) {
System.out.printf("Metrics: %s = %.2f%n", metric, value);
}
}
// AVOID: Double-checked locking (verbose, tricky to get right)
static class LegacySingleton {
private static volatile LegacySingleton instance; // volatile required!
private LegacySingleton() {}
public static LegacySingleton getInstance() {
if (instance == null) { // first check (no lock)
synchronized (LegacySingleton.class) {
if (instance == null) { // second check (with lock)
instance = new LegacySingleton();
}
}
}
return instance;
}
}
public static void main(String[] args) {
// Enum singleton
AppConfig cfg = AppConfig.INSTANCE;
cfg.setDbUrl("jdbc:postgresql://prod/mydb");
System.out.println(AppConfig.getInstance().getDbUrl());
System.out.println(cfg == AppConfig.INSTANCE); // true โ same instance
// Holder idiom
MetricsRegistry reg = MetricsRegistry.getInstance();
reg.record("requests_per_sec", 1234.5);
System.out.println(reg == MetricsRegistry.getInstance()); // true
}
}Sources
- https://www.pearson.com/en-us/subject-catalog/p/effective-java/P200000000138 โ Effective Java, Item 3
- https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom โ Holder idiom