How To Initialize A Singleton With Double-Checked Locking in Java
We’ve all struggled with understanding concurrency in Java. Even though your code looks and works fine on your system, concurrency bugs are uninvited and the code might still be broken and fail at any moment.
In this post, we will look at one of the methods to initialize a Singleton class in Java. The singleton is expected to be used by multiple threads and only one instance must be created.
Let’s first look at how it’s done when it is expected to be used by a single thread. We can then find the problems and correct them so that it works in a multi-threaded environment.
Initializing Singleton in a single-threaded environment
This works perfectly fine for single-threaded applications. But, what if two or more threads enter the if block at the same time? They will definitely succeed in the condition since `instance` is not yet initialized. This leads to the creation of multiple objects of the Singleton class.
One of the most common approaches to such concurrency problems is to use the synchronized keyword. If any code is wrapped inside a synchronized block or using a synchronized method, you can restrict access to only a single thread. The other threads will have to wait until the current thread exits the synchronized block.
Using synchronization while creating the object
So how do you apply the synchronized keyword to the above code so that only one thread is allowed access inside the if block when multiple threads try to create the instance.
One of the ways is to just make the entire method synchronized.
This works fine, but performance-wise it's a big no-no. The above method restricts access only to a single thread every time. If the instance is created properly, we don't really need to restrict the access to it, since the if condition fails and the instance is returned.
The solution here is to restrict the access only while creating the instance. You might think of doing something like this:
Just taking a lock on the class while creating the object doesn’t work. Suppose thread A and B both enter the if-block and let's say thread A gets a lock and creates a new object. After it exits, thread B takes a lock and creates another object.
This is where double-checked lock comes into play. This error can be avoided just by putting another if condition inside the synchronized block. So, even if two threads access the first if-block, one gets a lock and creates the object, another thread will still check if the object is created or not.
This is how double-checked locking is implemented:
This is how our code looks finally.
Looks fine, isn’t it? But wait. There is still a bug with this code.
Imagine a thread creates an instance and another thread sees that the instance is not null and returns it. But why does this create a problem? This has to do with the fact that the JVM allows the publication of partially initialized objects. Thread A might assign some memory space for the instance object without completely initializing it. Thread B might see that the instance is not null and return the partially constructed one, which might cause subtle bugs. The solution is to make the instance volatile.
This is how you initialize a Singleton with double-checked locking: