Creational Design Patterns
Some resources in a system are, by their very nature, singular. Imagine you have a class that manages the connection pool to your database or reads an application's configuration from a .properties file.
If multiple parts of your application could create their own DatabaseConnectionPool instances, you'd quickly exhaust the available database connections. If they each created their own ConfigManager, it would be incredibly inefficient, and worse, if one instance changed a setting at runtime, the other instances wouldn't know, leading to inconsistent and unpredictable application behavior.
The challenge is this: how do you enforce a rule that a class can only ever have one instance, while also making that single instance easily accessible to the rest of your application?
The Singleton pattern is a creational pattern that solves this exact problem. Its intent is to:
"Ensure a class only has one instance, and provide a global point of access to it."
It's a dual-purpose pattern. It both restricts the instantiation of a class to a single object and provides a single, well-known entry point to get that object.
Implementing a Singleton involves three key ingredients:
private static field: This holds the one and only instance of the class.private constructor: This is the most critical step. It prevents any other class from using the new keyword to create an instance, thus blocking any "rogue" instantiations.public static method (usually named getInstance()): This is the global access point. Any client that needs the instance calls this method. It's responsible for creating the instance the very first time it's called (a technique called lazy initialization) and then returning that same instance on all subsequent calls.For an interview, simply showing a basic Singleton isn't enough. Discussing the challenges, particularly thread safety, is where you demonstrate depth. Let's look at the evolution in Java.
Attempt 1: Eager Initialization (Simple & Thread-Safe)
public class EagerSingleton {
// 1. Instance is created when the class is loaded.
private static final EagerSingleton instance = new EagerSingleton();
// 2. Private constructor
private EagerSingleton() {}
// 3. Global access point
public static EagerSingleton getInstance() {
return instance;
}
}
Attempt 2: Lazy Initialization (Not Thread-Safe!)
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {}
public static LazySingleton getInstance() {
// PROBLEM: Two threads could pass this check at the same time!
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
This is a "lazy" implementation, but it will fail in a multithreaded environment. This is a common flaw to point out in an interview. To fix this, developers historically reached for a more complex solution.
Attempt 3: Double-Checked Locking
To avoid the performance cost of synchronized on every call to getInstance(), developers created the Double-Checked Locking (DCL) pattern. The idea is to only enter the synchronized block if the instance is null.
However, this pattern is famously broken without the volatile keyword.
public class DCLSingleton {
// The 'volatile' keyword is essential here!
private static volatile DCLSingleton instance;
private DCLSingleton() {}
public static DCLSingleton getInstance() {
// First check (not synchronized)
if (instance == null) {
// Synchronize only when instance is null
synchronized (DCLSingleton.class) {
// Second check (synchronized)
if (instance == null) {
instance = new DCLSingleton();
}
}
}
return instance;
}
}
Why is volatile so important here?
This is a deep topic perfect for an interview discussion. Without volatile, you can have a "partially constructed" object. The line instance = new DCLSingleton(); is not atomic. It can be broken down by the compiler and CPU into roughly three steps:
DCLSingleton object.instance variable.The problem is that the Java Memory Model allows the compiler or CPU to reorder these steps for performance. A thread (Thread A) might perform step 1 and 3 before step 2.
Imagine this sequence:
instance is null.instance. Now, instance is no longer null.getInstance(). It performs the first check (if (instance == null)). It sees that instance is not null and immediately returns it.The volatile keyword prevents this reordering. It establishes a "happens-before" relationship, guaranteeing that any write to the volatile variable (instance = ...) happens after all the steps in the constructor have completed.
Conclusion on DCL: While it works, it's overly complex and verbose. The Initialization-on-demand Holder pattern achieves the same result with much simpler, cleaner code that is easier to understand and guaranteed to be correct by the JVM. You should know DCL, but recommend IODH as the superior solution.
The Modern Recommended Approach: Initialization-on-demand Holder
This pattern is the standard, recommended approach in Java. It is lazy, thread-safe, and far less complex than older techniques like double-checked locking.
public class OnDemandSingleton {
// 1. Private constructor
private OnDemandSingleton() {}
// 2. A private static inner class holds the instance.
// This class is not loaded until getInstance() is called.
private static class SingletonHolder {
private static final OnDemandSingleton INSTANCE = new OnDemandSingleton();
}
// 3. Global access point
public static OnDemandSingleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
This approach is both lazy (the SingletonHolder class is only initialized when getInstance() is called) and thread-safe (the JVM guarantees that the class loading process is thread-safe).
The Enum Singleton
For the most robust, concise, and safest Singleton implementation in Java, you can use a simple enum. This approach is thread-safe out-of-the-box and provides protection against creating multiple instances through serialization or reflection.
public enum EnumSingleton {
INSTANCE;
// You can add methods here as needed
public void doSomething() {
System.out.println("Enum Singleton is doing something.");
}
}
// How to use it:
// EnumSingleton.INSTANCE.doSomething();
Why is the Enum Singleton so effective?
Serializable. When a serialized Singleton is deserialized, a new instance is created. The enum implementation handles this automatically, ensuring only one instance exists.private constructor of other Singleton implementations to create new instances. The JVM explicitly prevents this for enums, making them immune to this issue.For any new code, the Enum Singleton is almost always the best choice if you need a Singleton in Java.
While classic, the Singleton pattern is often considered an "anti-pattern" by modern software design standards. Being able to articulate why is crucial.
The Drawbacks:
Singleton.getInstance() out of nowhere.When is it Acceptable? Despite the drawbacks, it can be a pragmatic choice for genuinely singular, stateless resources like a logger, a hardware interface, or a thread pool.