Singleton Pattern
Singleton Pattern
The Singleton pattern guarantees that a class has exactly one instance throughout the lifetime of the application, and provides a single global access point to that instance. It is one of the most widely used — and most frequently misused — creational patterns in the GoF catalogue.
When is a Singleton appropriate?
A class should be a singleton when two conditions are both true:
- Exactly one instance must exist — e.g. a configuration registry, a connection pool, a thread-safe cache, a hardware interface driver.
- That single instance must be accessible from many places without passing it through every constructor or method call.
Eager Initialization
The simplest form: the instance is created when the class is loaded by the JVM. Because class loading is thread-safe by the JVM spec, no synchronization is needed.
Trade-off: The instance is created even if it is never used. For lightweight objects this is fine. For expensive resources (e.g. a database connection pool) that might not always be needed, lazy initialization is preferable.
Lazy Initialization — the Double-Checked Locking idiom
Lazy initialization defers creation until the first call to getInstance(). In a concurrent environment a naive if (instance == null) check is not safe — two threads can both see null and each create an instance. The classic fix is double-checked locking combined with the volatile keyword:
volatile mandatory here? Without it the JVM is allowed to reorder the write to INSTANCE so that another thread might observe a partially-constructed object. volatile imposes a happens-before relationship: the full construction completes before the reference is published. Omitting volatile is a subtle data race that causes hard-to-reproduce bugs on multi-core hardware.
The Initialization-on-Demand Holder idiom
A cleaner lazy alternative that avoids explicit synchronization entirely, exploiting the JVM guarantee that a class is initialized at most once and only when first accessed:
The Enum Singleton — the gold standard
Joshua Bloch's Effective Java (Item 3) recommends implementing Singleton as a single-element enum. This is the most concise, thread-safe, and serialization-safe approach available in Java:
The enum approach defeats three classic singleton-breaking attacks:
- Reflection:
Constructor.setAccessible(true)throws anIllegalArgumentExceptionfor enum constructors — the JVM itself enforces this. - Serialization: By default, deserializing a class produces a new instance, breaking the invariant. Enum serialization is handled by the JVM and always returns the single canonical instance — no
readResolve()override needed. - Cloning:
Enumdoes not implementCloneable, soclone()throwsCloneNotSupportedException.
java.lang.Enum). If your Singleton must inherit from an abstract base class or be created lazily with complex initialization logic, use the Holder idiom instead. But for the common case — a stateless or simply-stateful singleton — the enum form is ideal.
Thread Safety Comparison
- Eager init: Thread-safe; no extra work required. Instance always created.
- Double-checked locking: Thread-safe with
volatile; lazy. Common in legacy code. - Holder idiom: Thread-safe via JVM class-init semantics; lazy. Recommended for class-based singletons.
- Enum singleton: Thread-safe; serialization-safe; reflection-proof. Recommended by Effective Java.
Singletons and Dependency Injection
A common professional pattern is to combine the Singleton scope with DI. Frameworks like Spring manage singleton beans for you — you declare a @Service or @Component and the container ensures one instance. You gain all the testability of DI without hand-rolling double-checked locking.
When you write raw Singletons (outside a DI container), program to an interface so callers depend on the abstraction, not the concrete singleton class. This preserves testability:
Summary
Use eager initialization when the cost of creating the instance is low. Use the Holder idiom when lazy initialization for a class-based singleton is required. Use the enum singleton as the default choice for any new singleton that does not need to extend a class — it is concise, thread-safe, serialization-safe, and reflection-proof. Understand double-checked locking to maintain legacy code safely.