Singletons are a versatile tool. Here are implementations that consider its use with eager loading, lazy loading, and multi-threaded implementations.
The singleton design pattern is basically used to control the creation of a number of objects, hence belonging to the Creation pattern family.
The earlier trend was to create a max of one object, but in some situations, we need a fixed number of objects; and this pattern is right there to help us. Generally, we mark the constructor as private to ensure that the outside world cannot create objects at all and provides one static method that simply returns the object. It creates an object only when there is no creation beforehand.
With time, people realized several problems with this vanilla implementation of singletons, and it was improved to address those problems. Note that singletons aren’t right or wrong; as long as they fit into your problem domain.
In this talk, we will look into various implementations of singletons.
Let us look at the class diagram:
There are several places where it is advisable to use the singleton pattern. For example: logging, caching, load balancing, configuration, communication (IO or remote to avoid poor performance) and DB connection-pooling. One example of a singleton in the Java API is the Runtime class.
Here’s a vanilla way of implementing a singleton using an eager loading mechanism, which is thread-safe as well.
On the other hand, here’s an example of implementing a singleton using a lazy loading mechanism. In the case of a multi-threaded application, it would be a bad approach.
A multi-threaded approach can avoid the race condition to ensure it won’t violate the philosophy of a singleton. But in the example below, making the whole method ’synchronized‘ is not a good approach, as we need to put the lock on the object creation statement only.
The below multi-threaded way of implementation can avoid the race condition to ensure it won’t violate the philosophy of singleton and with the help of double-checked locking using object-level lock will achieve the same. This implementation guarantees thread safe; but the extra object is required to just keep a lock which is not a good practice. Another downside is that someone can take the advantage of using class-level lock as your lock is on a different object.
Another multi-threaded-based implementation (to avoid race conditions) can be achieved with the help of double-checked locking using a class-level lock. Here, marking the MySingleton object as volatile will ensure that changes made by one thread should be visible in another. This implementation guarantees thread safety.
This means of implementation provides an intelligent constructor that will stop the singleton contract violation using reflection.
Here’s a very popular implementation using a static class, which brings the powers of lazy loading and thread safety.
In some circumstances, if your singleton class is inheriting Cloneable interface properties, then your singleton class needs extra care to prevent the singleton design contract. Your singleton class should override the clone method and explicitly throws the CloneNotSupportedException.
Another, and our final, very popular and smart way of implementing singletons is using enum, which takes care of all the issues we’ve so far.
Sometimes, people talk about singletons across multiple JVMs, so let’s touch on that. Singleton means only one object and we know very well that the object lifecycle is managed by the JVM, so one shared object across multiple JVMs is not possible.
But if you need to, you can probably create the object in one JVM and distribute it as a serialized object that could be used by other JVMs (but keep in mind that you are deserializing it, so keep in mind that anything static or marked as transient will not be achieved, and somewhere, the singleton contract is breaking away) . You can also try using RMI server objects as singletons to fit your needs.
Happy learning!