Singleton Pattern
Category
Creational Design Pattern
Overview
The Singleton Pattern is a creational design pattern that ensures a class has only one instance throughout the lifetime of an application. It provides a global point of access to that instance, ensuring consistency and centralized control over shared resources or global states.
This pattern is particularly useful when:
A shared resource needs to be accessed globally.
It is critical to maintain a single state across the application.
Key Characteristics
Global Access:
Provides a single access point to the instance through a static method.
Controlled Instantiation:
Ensures that only one instance exists by making the constructor private.
Lazy or Eager Initialization:
Controls when the instance is created:
Lazy Initialization: The instance is created only when accessed for the first time.
Eager Initialization: The instance is created during class loading.
Thread Safety:
Ensures that the Singleton instance is safely shared across threads using synchronization or modern static initialization techniques.
Encapsulation:
The Singleton class encapsulates its own instantiation logic, adhering to the Single Responsibility Principle.
UML Diagram
The UML diagram below illustrates the Singleton Pattern. The static getInstance method ensures a single instance of the class is created and shared.
Implementation Variations
1. Eager Initialization
The instance is created at the time of class loading, ensuring simplicity but potentially wasting resources if unused.
public class EagerSingleton {
private static final EagerSingleton INSTANCE = new EagerSingleton();
private EagerSingleton() {}
public static EagerSingleton getInstance() {
return INSTANCE;
}
}
2. Lazy Initialization
The instance is created only when it is first accessed, saving resources if it is not immediately needed.
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {}
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
3. Thread-Safe Initialization
Ensures that the Singleton instance is safely shared across multiple threads.
Double-Checked Locking
public class ThreadSafeSingleton {
private static volatile ThreadSafeSingleton instance;
private ThreadSafeSingleton() {}
public static ThreadSafeSingleton getInstance() {
if (instance == null) {
synchronized (ThreadSafeSingleton.class) {
if (instance == null) {
instance = new ThreadSafeSingleton();
}
}
}
return instance;
}
}
4. Enum Singleton
Provides a thread-safe and serialization-safe Singleton implementation. This approach is unique to Java.
public enum EnumSingleton {
INSTANCE;
public void doSomething() {
System.out.println("Enum Singleton is working!");
}
}
Example: Logging System
A logger is a common use case for the Singleton Pattern. It ensures that all parts of the application log messages through the same instance.
Implementation
public class Logger {
private static Logger instance;
private Logger() {}
public static Logger getInstance() {
if (instance == null) {
synchronized (Logger.class) {
if (instance == null) {
instance = new Logger();
}
}
}
return instance;
}
public void log(String message) {
System.out.println("Log: " + message);
}
}
Client Code
Logger logger = Logger.getInstance();
logger.log("Singleton logger example.");
Output
Log: Singleton logger example.
Use Cases
Logging Service:
A single logging instance ensures consistent formatting and centralized log output.
Configuration Manager:
Centralized access to application settings or configuration.
Resource Management:
Manages shared resources such as database connections or thread pools.
State Tracking:
Centralizes and maintains a global state, such as an application session or cache.
Advantages and Disadvantages
Advantages
Controlled Access:
Simplifies and centralizes access to a shared resource or state.
Resource Efficiency:
Prevents redundant object creation.
Global Consistency:
Ensures a single, shared instance across the application.
Thread Safety:
Modern implementations ensure safe access in concurrent environments.
Disadvantages
Global State Dependency:
Overuse can lead to tightly coupled code, making testing and maintenance more challenging.
Difficulty in Testing:
Mocking or replacing Singletons in unit tests can be complex.
Potential Resource Waste:
An eagerly initialized Singleton may consume resources even if it’s never used.
Key Takeaways
The Singleton Pattern is a powerful tool for ensuring global consistency and controlled access to shared resources. However, it should be used judiciously to avoid pitfalls like tightly coupled code or hidden dependencies.
Use it when: A single instance is needed to manage a shared resource or maintain a global state.
Avoid it when: Simpler solutions suffice, or when testing flexibility is paramount.