63 Why Must We Add Volatile to Double Checked Locking Singleton Pattern

63 Why Must We Add volatile to Double-Checked Locking Singleton Pattern #

In this lesson, we will mainly explain why “volatile” is necessary in the double-checked locking pattern of the singleton pattern.

What is the Singleton Pattern? #

The singleton pattern ensures that a class has only one instance and provides a global access point to it.

Why do we need the Singleton Pattern? #

So why do we need a singleton? One reason is to save memory and computation. In many cases, we only need one instance, and having more instances would be a waste.

Let’s take an example to illustrate this scenario. Consider a class that requires expensive initialization, as shown in the code below:

public class ExpensiveResource {

    public ExpensiveResource() {

        field1 = // query database

        field2 = // perform extensive calculations on the retrieved data

        field3 = // perform time-consuming operations like encryption and compression

    }

}

In this class, during construction, we need to query a database and perform extensive calculations on the retrieved data. So, during the first construction, we spend a lot of time initializing this object. However, suppose the data in the database is immutable. In that case, we can save this object in memory, so that in future development, we can directly use this same instance without the need to create a new instance. Creating new instances each time would result in more wastage, which is unnecessary.

Now let’s look at the second reason for needing a singleton, which is to ensure correct results. For example, if we need a global counter to count the number of people, having multiple instances would only create confusion.

Moreover, it is for the convenience of management. For many utility classes, we only need one instance. So, it is very convenient to use a unified entry point, such as the getInstance method, to obtain this singleton. Having too many instances is not helpful, but instead makes things complicated.

The general class structure of a singleton pattern is as shown in the following diagram: it has a private singleton object of the Singleton type, and the constructor is also private to prevent others from calling the constructor to create an instance. Additionally, there is a public getInstance method to obtain the singleton through this method.

img

Writing the Double-Checked Locking Pattern #

There are multiple ways to write a singleton pattern, but we will focus on the double-checked locking pattern that is strongly correlated with “volatile”. The code is shown below:

public class Singleton {

    private static volatile Singleton singleton;

    private Singleton() {

    }

    public static Singleton getInstance() {

        if (singleton == null) {

            synchronized (Singleton.class) {

                if (singleton == null) {

In the double-checked locking pattern, the “volatile” keyword is necessary to ensure the visibility of the singleton instance across multiple threads. Without “volatile”, it is possible for one thread to see a partially constructed object, leading to unexpected behavior. By using “volatile”, we can guarantee that writes to the singleton instance variable are visible to other threads, preventing such issues.

singleton = new Singleton();

}
}

}

}

return singleton;

}

}

Here I will focus on explaining the getInstance method. In this method, we first check if (singleton == null), then there is a synchronized block, followed by another if (singleton == null) check, and finally singleton = new Singleton() to create the instance.

We perform two if (singleton == null) checks, which is why it is called “double-checked locking”. This writing style ensures thread safety. Suppose two threads arrive at the synchronized statement block at the same time, then the instantiation code will only be executed by the thread that acquires the lock first, and the other thread will find that singleton is not null in the second if statement and skip the instantiation statement. When other threads call the getInstance method later, they only need to check the first if statement, then skip the entire if block and directly return the instantiated object.

The advantage of this writing style is that it is not only thread-safe, but also has lazy loading and higher efficiency.

Now let’s talk about a common question. The interviewer may ask you, “Why double-check? Can we remove any of the checks?”

Let’s first look at the second check. In this case, you need to consider the following situation: two threads simultaneously call the getInstance method, and since singleton is null, both threads can pass the first if statement; then due to the presence of the lock mechanism, one thread will enter the synchronized statement and enter the second if statement, while the other thread will wait outside.

However, after the first thread executes the new Singleton() statement, it will exit the synchronized area. If there is no second check if (singleton == null), the second thread will also create an instance. This will break the singleton pattern and is not acceptable.

As for the first check, if it is removed, all threads will execute serially, which is inefficient. Therefore, both checks need to be kept.

Why do we need to use the volatile keyword in the double-checked locking pattern #

You may have noticed that we use the volatile keyword for the singleton object in the double-checked locking pattern. Why do we use volatile? The main reason is that singleton = new Singleton() is not an atomic operation. In fact, in the JVM, the above statement does at least three things:

  1. Allocate memory space for singleton.
  2. Call the constructor of Singleton to initialize it.
  3. Point the singleton object to the allocated memory space (after this step, singleton is no longer null).

Note the order of steps 1-2-3 because there is an optimization called instruction reordering, which means that the order of steps 2 and 3 cannot be guaranteed. The final execution order can be 1-2-3 or 1-3-2.

If the order is 1-3-2, then after step 3, singleton is not null, but step 2 has not been executed yet. The singleton object is not fully initialized, and its properties’ values may not be the expected values. Suppose thread 2 enters the getInstance method at this time. Because singleton is not null, it will pass the first check and directly return the singleton object. However, the singleton has not been fully initialized at this time, so using this instance will cause an error. The detailed process is shown in the following diagram:

  1. Thread 1 first performs step 1 of creating a new instance, i.e., allocating memory space for the singleton object. Due to the reordering of the thread 1, step 3 of creating a new instance is executed, which is to point singleton to the previously allocated memory address. After step 3, the singleton object is no longer null.
  2. Thread 2 enters the getInstance method, checks that singleton is not null, and then returns the singleton object and uses it. Since it has not been initialized, an error occurs. Finally, thread 1 executes step 2 of creating a new instance, but the initialization is too late because an error has been reported earlier.

Using volatile indicates that the update of the field may occur in another thread, so when reading a value written by another thread, the following operations can be smoothly performed. In the JMM used in JDK 5 and later versions, using volatile to some extent prohibits the reordering of relevant statements, thereby avoiding the problem of reading incomplete objects caused by reordering.

By using volatile, we can prevent the situation where an uninitialized object is obtained, thus ensuring thread safety.

Summary #

In this lesson, we first introduced what a singleton pattern is and why it is necessary to use it. Then we introduced the double-checked locking pattern and why we need double-check when using this pattern. The reason why we need to use volatile is mainly to ensure thread safety.