Compare thread-safe Singleton implementation styles in Java and choose the smallest one that matches the lifecycle requirement.
Thread-safe singleton: A singleton implementation whose construction and publication remain correct when multiple threads access it concurrently.
Java offers several safe ways to implement a singleton, but they are not interchangeable in readability or lifecycle behavior. The best one depends on whether you need eager creation, lazy creation, or container-managed ownership.
1public final class MetricsRegistry {
2 private static final MetricsRegistry INSTANCE = new MetricsRegistry();
3
4 private MetricsRegistry() {}
5
6 public static MetricsRegistry getInstance() {
7 return INSTANCE;
8 }
9}
This is thread-safe because class initialization is handled safely by the JVM. If eager construction is acceptable, this is often the clearest implementation.
1public final class SettingsStore {
2 private static SettingsStore instance;
3
4 private SettingsStore() {}
5
6 public static synchronized SettingsStore getInstance() {
7 if (instance == null) {
8 instance = new SettingsStore();
9 }
10 return instance;
11 }
12}
This is correct, but it adds synchronization to every access. In many modern Java codebases, it is more often a teaching example than the best production default.
1public final class ConnectionManager {
2 private static volatile ConnectionManager instance;
3
4 private ConnectionManager() {}
5
6 public static ConnectionManager getInstance() {
7 if (instance == null) {
8 synchronized (ConnectionManager.class) {
9 if (instance == null) {
10 instance = new ConnectionManager();
11 }
12 }
13 }
14 return instance;
15 }
16}
With volatile, this is valid in modern Java. But it is also more complex than the eager version or the holder idiom. Use it only when lazy initialization really matters and simpler approaches do not fit.
1public final class AuditSink {
2 private AuditSink() {}
3
4 private static final class Holder {
5 private static final AuditSink INSTANCE = new AuditSink();
6 }
7
8 public static AuditSink getInstance() {
9 return Holder.INSTANCE;
10 }
11}
This gives you lazy initialization with JVM-backed class-loading safety and less concurrency noise than double-checked locking.
The right question is not “which version is most advanced?” It is:
If eager construction is fine, take the eager version. If laziness matters, the holder idiom is often clearer than hand-written lock choreography.
When reviewing thread-safe singleton code, ask:
Correctness matters, but clarity matters too. Thread safety should not become a trophy implementation.