Introduction
The Singleton Design Pattern is a creational design pattern that ensures that a class has only one instance, while also providing a global access point to this instance. It is used when only a single instance of a class should control the action throughout the execution and provide a single point of access to that instance.
Applications of Singleton Pattern
A singleton is a design pattern that is used to ensure that a class has only one instance and to provide a global access point to that instance. It is a useful pattern in situations where a single instance of a class must coordinate actions across the system. A few examples of real-world use cases for singletons include:
- A logging service, where a single instance of the logging class is used to write log entries to a file or database.
- A configuration manager, where a single instance of the configuration class is used to manage application settings and configurations.
- A thread pool, where a single instance of the thread pool class is used to manage and schedule tasks.
- A database connection pool, where a single instance of the connection pool class is used to manage and reuse database connections.
- A resource manager, where a single instance of the resource manager class is used to manage and allocate resources such as memory, file handles, and network connections.
Classic Implementation
The classic implementation of the Singleton Design Pattern in Java involves creating a static instance of the class within the class, and a static method that returns the instance. This is the most basic implementation and it is not thread-safe.
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
Thread-Safe Implementation
To make the classic implementation thread-safe, the getInstance()
method can be synchronized.
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
Double Checked Locking
The Double Checked Locking implementation uses a double-checked locking mechanism to reduce the use of synchronization in the getInstance()
method. This improves performance and reduces the use of resources.
public class Singleton {
private volatile static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
The instance
variable is declared as volatile
which means that its value can be changed by multiple threads and that any change made to the variable will be visible to all threads immediately.
The getInstance()
method is the main method that is used to get the singleton instance of the class. The method first checks if the instance
variable is null
. If it is, it enters a synchronized block where it checks again if the instance
variable is null
. If it is still null
, it creates a new instance of the Singleton
class and assigns it to the instance
variable.
The reason for the double-check is that multiple threads might enter the synchronized block and each thread will create a new instance of the Singleton class, which is not the desired behavior. By checking if the instance is null
before entering the synchronized block, we can reduce the number of times the synchronized block is executed and improve the performance of the code.
The advantage of this solution is that it is thread safe and performs better than the classic singleton implementation in terms of performance, as it avoids unnecessary synchronization.
Enum Singleton
In Java, enums are guaranteed to be thread-safe and only one instance of an enum value is created by the JVM. Therefore, using an enum to implement the Singleton Design Pattern is a good option.
public enum Singleton {
INSTANCE;
}
The Enum Singleton solution is considered the most efficient because it uses the features of the Java language itself to ensure that only a single instance of the Enum is created. When an Enum is defined, the JVM instantiates a single instance of the Enum, and any further attempts to instantiate the Enum will return a reference to the same object. Additionally, Enum instances are guaranteed to be unique and thread-safe because the JVM handles their creation. This means that the Enum Singleton pattern does not require any additional synchronization or locking mechanisms, which can improve performance and reduce resource usage. Furthermore, Enum Singleton also provides a way to overcome the serialization issues of classic singleton.
Conclusion
It’s important to note that the best implementation method depends on the specific requirements of your application. The classic implementation is simple, but the thread-safe and double-checked locking implementation can improve performance and reduce the use of resources. The Enum Singleton is a good option as it is guaranteed to be thread-safe and only one instance is created by the JVM.
Below is a comparison table between the options
Method | Advantages | Disadvantages |
---|---|---|
Classic | Simple and easy to implement | Not thread-safe |
Thread-safe | Guarantees thread-safety | Increases performance overhead |
Double-checked Locking | Improved performance and reduced use of resources | More complex implementation |
Enum Singleton | Guaranteed thread-safety and only one instance is created by the JVM | Limited flexibility |