Singleton Pattern | Handling Multithreading and Performance Optimizations
Definition
A Singleton Pattern is a creational design pattern that ensures that a class has only one instance and also provides a global access to it.
Introduction
As the definition states, the Singleton Pattern only allows its users to create a single instance of the class. Is it worth it to restrict a class implementation to have a single instance? The singleton pattern has some drawbacks but also has some interesting applications where we need only one single object, for eg. Thread Pools, Caches, Loggers etc.
Let’s take the example of a Cache. We want a single cache object to be there throughout the runtime and all the clients should look for the cached data in that single object.
Having a multiple Cache instance can cause many unwanted problems. Suppose you instantiate two cache objects during runtime and a client puts a key in one cache instance and requests for the same key from a different instance, This can cause the issue of data unavailability in your cache.
You saw how the presence of multiple instances of the cache class caused the data unavailability issue in your code.
The Singleton Pattern can be used to deal with such issues by ensuring only one object of a class is created throughout the runtime and every client has a global access to it.
Lazy Initialization
Let’s dive into the implementation of the singleton pattern. We will discuss the Lazy Initialization method where the instance of the singleton class is only created when it is accessed for the first time.
It’s called a Lazy initialization because we wait for a client to access the class or request for its first instance and only then creates one.
In the above implementation we have kept the constructor of the class private so that no one could create the instance of the class from outside.
The above statement won’t work.
Instead the clients will have to rely on the static method of the class to request for the Cache object.
Now, if a client requests for the object for the first time then a new instance of the class will be created and returned. Any subsequent calls to that static method post that will return the previously created instance.
Dealing with Multiple Threads
The previous implementation we dealt with is not thread safe. If multiple threads enter the getCache static method then more than one cache objects can get created and returned.
This will again start causing data unavailability issues in our code.
Recommended by LinkedIn
There are multiple ways to avoid the above scenario. Let’s discuss the solutions.
Synchronized Implementation
We can fix the above issue of multi-threading by making the static getCache() a synchronized method.
By adding a synchronized keyword to getCache we force every thread to wait for its turn before it can enter the method. This will avoid two threads accessing the static method at the same time.
Although we have solved the issue of Multi-threading, the above implementation can perform poorly when a huge traffic tries to access our cache service.
If we look carefully we only need the synchronized keyword for the first time to ensure multiple instances of the cache service are not created. Once a thread creates the first instance of the cache service, we don’t need to synchronize anymore. Once the first instance is created it's perfectly fine for multiple threads to access the static getCache() method at the same time. Since every time the already created instance will be blindly returned.
Hence the synchronized implementation will only create performance overheads after the first call to the static method.
Eager Initialization
In the previous section we saw the performance overhead caused by the synchronized method in multithreading. One way to improve the performance is to eagerly create the instance of our Cache service when the class is loaded.
It is guaranteed that the unique instance of the Cache service is created before any thread accesses the static getCache() method.
Conclusion
We discussed the Singleton Pattern in-depth along with its Lazy Implementation. We also discovered the issue with Multithreading along with the performance overheads caused by the Synchronized implementation of the design pattern. We also discussed the Eager Implementation of the design pattern and performance optimisations achieved by the same.
You can find the sample code for all the implementations discussed in this edition on my Github repo. Feel free to check it out!
Simple and informative. Great share Saurav. Thank you.
Great article Saurav Prateek! It cleared my multi-threaded bottleneck doubt.
http://lldcoding.com
Thank you Saurav, great share
Saurav Prateek highly appreciate the efforts you put into the diagrams. It's awesome.