3 minutes
Singleton Design Pattern
Singleton design pattern is a creational design pattern that restricts instantiation of a class ensuring class has only one instance and provide a global point of access to it.
Common Implementations
- Lazy Initialization: ensures instance is created only when it is first requested.
- Eager Initialization: instance is created when the class is loaded.
- Thread-Safe Implementations: ensures the singleton works correctly in multi-threaded environments, such as using
synchronizedmethods or double-checked locking. (though double-checked locking has nuances and is sometimes considered an anti-pattern in modern Java)
Use Cases
- Logging
- Configuration settings
- Thread pools
- Caches
- Device drivers
Examples
Eager initialization
public class Singleton {
private static final Singleton instance = new Singleton();
private EagerInitializedSingleton(){}
public static Singleton getInstance() {
return instance;
}
}
- Useful if your singleton class is not using a lot of resources.
Using synchronized method (Lazy initialization)
public class Singleton {
// Volatile ensures visibility and prevents reordering
private static volatile Singleton instance;
private Singleton() {
}
// Thread-safe with method-level synchronization
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
public void example() {
System.out.println("Hello from Singleton!, (using synchronized method)");
}
}
- Lazily initialization the instance.
synchronizedensures that only one thread can enter the method at a time.- It ensures lazy initialization — Singleton instance is created only when needed.
- Performance overhead: Every call to
getInstance()acquires a lock, even after the instance has been initialized. This is unnecessary and can be expensive under heavy load.
Double-Checked Locking (Lazy initialization)
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) { // <- Double-check
instance = new Singleton();
}
}
}
return instance;
}
public void example() {
System.out.println("Hello from Singleton!, (double-check)");
}
}
- Lazily initialization the instance.
- double check aims to minimize the overhead of synchronization.
- Complex
Bill Pugh (Inner Class) - Recommended Approach
public class Singleton {
private Singleton() {
// Prevent reflection attack
if (SingletonHelper.INSTANCE != null) {
throw new RuntimeException("Use getInstance() method to get the single instance of this class");
}
}
private static class SingletonHelper {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHelper.INSTANCE;
}
public void example() {
System.out.println("Hello from Singleton!, (Bill Pugh)");
}
}
- Static inner helper class is loaded only when
getInstance()is called. - Class loading is thread-safe.
- No need for synchronized, and lazy initialization is achieved.
Drawbacks
Despite its utility, the Singleton pattern is sometimes criticized as an anti-pattern because it can:
- Creates global state: Leading to tight coupling and potential side effects.
- Makes Testability difficult: Making unit testing challenging due to hidden dependencies.
- Violate the Single Responsibility Principle: When the singleton takes on responsibilities beyond managing its own instance.