Singleton is a creational design pattern that ensures a class has only one instance while providing a global access point to this instance.
Singleton pattern principles
- Singleton’s design pattern ensures that a class has only one instance and must restrict the instantiation of the class to a single object.
- It provides a global access point to the instance and allows other classes and components to easily access the Singleton instance.
Java singleton pattern implementation
Implementing a singleton pattern, we have different approaches, but all of them have the following familiar concepts.
- A private constructor is used to restrict the instantiation of the class from other classes.
- The private static variable of the same class is the only instance of the class.
- The public static method returns the class instance, which is the global access point for the outer world to get the example of the singleton class.
1. Eager initialization
In eager initialization, the singleton class instance is created during class loading.
public class EagerInitializedSingleton {
private static final EagerInitializedSingleton instance = new EagerInitializedSingleton();
// private constructor to avoid client applications using the constructor
private EagerInitializedSingleton(){}
public static EagerInitializedSingleton getInstance() {
return instance;
}
}
This guarantees that the instance is always available but may consume resources even if it’s not needed immediately.
2. Lazy initialization
Lazy instantiation defers the instantiation of the singleton instance when it is needed. This approach conserves resources.
public class LazyInitializedSingleton {
private static LazyInitializedSingleton instance;
private LazyInitializedSingleton(){}
public static LazyInitializedSingleton getInstance() {
if (instance == null) {
instance = new LazyInitializedSingleton();
}
return instance;
}
}
The implementation works fine in a single-thread environment. Still, in a multi-thread system, multiple threads inside the if condition simultaneously can cause issues. This will destroy the singleton pattern, and both threads will get different instances of the singleton class.
3. Thread-safe singleton
The above example(Lazy initialization) of a singleton pattern is not thread-safe. To achieve thread safety, we can use the following approach.
1. Synchronize the getInstance() method
For the thread-safe singleton pattern, you need to use only a synchronized keyword on getInstead() method declaration. Here is the implementation
public class ThreadSafeSingleton {
private static ThreadSafeSingleton instance;
private ThreadSafeSingleton(){}
public static synchronized ThreadSafeSingleton getInstance() {
if (instance == null) {
instance = new ThreadSafeSingleton();
}
return instance;
}
}
Pros
- Thread safety is guaranteed.
- The client application can pass the parameters.
- Lazy initialization achieved.
Cons
- Slow performance because of locking overhead.
- Unnecessary synchronization that is not required once the instance variable is initialized.
2. Double-Checked Locking: Use the synchronized block inside the if block
Because of the getInstead() method’s locking overhead, using a synchronized block inside the method instead of the synchronized keyword on method declarations is faster and more efficient. Double-check locking is very useful in making it efficient.
public class ThreadSafeSingleton {
private static ThreadSafeSingleton instance;
private ThreadSafeSingleton(){}
public static ThreadSafeSingleton getInstanceUsingDoubleLocking() {
if (instance == null) {
synchronized (ThreadSafeSingleton.class) {
if (instance == null) {
instance = new ThreadSafeSingleton();
}
}
}
return instance;
}
}
Pros
- Thread safety is guaranteed.
- The client application can pass arguments.
- Lazy initialization achieved.
- Synchronization overhead is minimal and applicable only for the first few threads when the variable is null.
Cons
- Extra if condition.
Advantage of the singleton pattern
- Single instance creation
- Global Access
- Lazy initialization
- Thread safety
Disadvantages of the singleton pattern
- Global state: Introducing a global state complicates system behavior tracking and potentially leads to bugs.
- Tight coupling: Direct access to the singleton can result in tight coupling, making future changes or replacements difficult.
- Testing challenges: Global access complicates unit testing.
- Lifecycle management: Managing the singleton lifecycle, especially for re-initialization, can be complex.
- Limited extensibility: Extending or modifying Singleton classes can be challenging due to their restricted instantiation.
- Violate the single responsibility principle: The Singleton class has dual responsibilities: one is creating a single instance, and another is executing tasks. Thus, it violates the single responsibility principle.
Few Real-word singleton Examples:
- Logger: Manages logging operations across a system.
- Database Connection: Provides a shared connection for database interactions.
- Configuration manager: Centralizes application settings and properties.
- Cache manager: Handles caching operations to improve performance.
- Thread pool: Manages thread creation and execution in concurrent programming.
Reference
Thanks for reading.
See you! đź‘‹