In software development, we often require classes that can only have one object.
Example: thread pools, caches, loggers etc.
Creating more than one objects of these could lead to issues such as incorrect program behavior, overuse of resources, or inconsistent results.
This is where Singleton Design Pattern comes into play.
It is one of the simplest design patterns, yet challenging to implement correctly.
In this article, we will explore what it is, different ways you can implement it Java, real-world examples where it’s used and it’s pros and cons.
Singleton Pattern is a creational design pattern that guarantees a class has only one instance and provides a global point of access to it.
It involves only one class which is responsible for instantiating itself, making sure it creates not more than one instance.
To implement the singleton pattern, we must prevent external objects from creating instances of the singleton class. Only the singleton class should be permitted to create its own objects.
Additionally, we need to provide a method for external objects to access the singleton object.
In Java, one of the ways to implement Singleton is by making the constructor private and providing a static method for external objects to access it.
instance
class variable holds the one and only instance of the Singleton class.Singleton()
constructor is declared as private, preventing external objects from creating new instances.getInstance()
method is a static class method, making it accessible to the external world.There are several ways to implement the Singleton Pattern in Java, each with its own advantages and disadvantages.
Lets explore 7 of the most common methods.
This approach creates the singleton instance only when it is needed, saving resources if the singleton is never used in the application.
instance == null
).This implementation is not thread-safe. If multiple threads call getInstance()
simultaneously when instance
is null, it's possible to create multiple instances.
This approach is similar to lazy initialization but also ensures that the singleton is thread-safe.
This is achieved by making the getInstance()
method synchronized ensuring only one thread can execute this method at a time.
When a thread enters the synchronized method, it acquires a lock on the class object. Other threads must wait until the method is executed.
instance == null)
check and create the object.Although this approach is straightforward, using synchronized
can cause substantial overhead and reduce performance, which can be a bottleneck if called frequently.
This approach minimizes performance overhead from synchronization by only synchronizing when the object is first created.
It uses the volatile
keyword to ensure that changes to the instance variable are immediately visible to other threads.
instance == null)
passes, we synchronize on the class object.Although this method is a bit complex to implement, it can drastically reduce the performance overhead.
In this method, we rely on the JVM to create the singleton instance when the class is loaded. The JVM guarantees that the instance will be created before any thread access the instance variable.
This implementation is one of the simplest and inherently thread-safe without needing explicit synchronization.
static
variable ensures there's only one instance shared across all instances of the class.final
prevents the instance from being reassigned after initialization.This approach is suitable if your application always creates and uses the singleton instance, or the overhead of creating it is minimal.
While it is inherently thread-safe, it could potentially waste resources if the singleton instance is never used by the client application.
This implementation uses a static inner helper class to hold the singleton instance. The inner class is not loaded into memory until it's referenced for the first time in the getInstance()
method.
It is thread-safe without requiring explicit synchronization.
getInstance()
method is called for the first time, it triggers the loading of the SingletonHelper class.final
keyword ensures that the INSTANCE cannot be reassigned.The Bill Pugh Singleton implementation, while more complex than Eager Initialization provides a perfect balance of lazy initialization, thread safety, and performance, without the complexities of some other patterns like double-checked locking.
This is similar to eager initialization, but the instance is created in a static block.
It provides the ability to handle exceptions during instance creation, which is not possible with simple eager initialization.
It is thread safe but not lazy-loaded, which might be a drawback if the initialization is resource-intensive or time-consuming.
In this method, the singleton is declared as an enum rather than a class.
Java ensures that only one instance of an enum value is created, even in a multithreaded environment.
The Enum Singleton pattern is the most robust and concise way to implement a singleton in Java.
Many Java experts, including Joshua Bloch, recommend Enum Singleton as the best singleton implementation in Java.
It may not always be suitable especially if you need to extend a class or if lazy initialization is a strict requirement.
Singleton is useful in scenarios like:
It's important to note that the Singleton pattern should be used judiciously, as it introduces global state and can make testing and maintenance more challenging. Consider alternative approaches like dependency injection when possible to promote loose coupling and testability.