Nassim MOUALEK
Nassim MOUALEK

Reputation: 4864

Why adding the second test in the Double-Checked-Locking?

Refering to this https://en.wikipedia.org/wiki/Double-checked_locking, we have:

// "Double-Checked Locking" idiom
class Foo {
    private Helper helper;
    public Helper getHelper() {
        if (helper == null) {
            synchronized(this) {
                if (helper == null) {
                    helper = new Helper();
                }
            }
        }
        return helper;
    }    
    // other functions and members...
}

What is the purpose of the second test? Is it really possible that 2 threads could access the same critical section at the same time?

Upvotes: 1

Views: 404

Answers (3)

ekostadinov
ekostadinov

Reputation: 6940

In addition to all other answers, a very good example is the Thread Safe Singleton pattern. You have again the same:

public static Singleton getInstanceDC() {
   if (_instance == null) {       // Single Checked
    synchronized (Singleton.class) { 
         if (_instance == null) { // Double checked 
               _instance = new Singleton();
          }
     }
  }
   return _instance;
}

So basically performing the lock is a lot more expensive if compared to a pointer check instance != null. The implementation also has to ensure that when the Singleton is initialized there will be no issues resulting from thread race conditions. So the main reason is the performance. If instance != null (which will always be the case except the very first time), there is no need to do the expensive lock: Two threads accessing the initialized singleton simultaneously would be synchronized unnecessarily. This picture demonstrates it clear:

enter image description here

There is a lot more in Singletons then just the double checking:

  • Early and lazy instantiation in singleton pattern

  • Singleton and Serialization

Upvotes: 1

user555045
user555045

Reputation: 64904

To make it more obvious what could happen, consider this code:

if (helper == null) {
    Thread.sleep(1000);
    synchronized(this) {
        if (helper == null) {
            helper = new Helper();
        }
    }
}

This is just an example so I don't care about InterruptedException.

It should be clear that between passing the first test and entering the critical region, there is time for some other thread to come along and enter the critical region first.

Upvotes: 2

Nayuki
Nayuki

Reputation: 18533

Two threads cannot access the same critical section at the same time; by definition a critical section is mutually exclusive.

To answer your question, the first null test is a cheap test without synchronization, and the second null test checks the real state with synchronization.


The second test is necessary. Suppose we didn't have it, and the code looked like this:

public Helper getHelper() {
    if (helper == null) {
        synchronized(this) {
            helper = new Helper();
        }
    }
    return helper;
}

Suppose thread A executes if (helper == null) and it tests true, so it goes into the if block but execution gets suspended. No variables are updated yet.

Then thread B executes if (helper == null) and it tests true, so it goes into the if block. Then it continues execution into the synchronized block and initializes the helper object and returns.

Now thread A continues execution, goes into the synchronized block, overwrites helper with a new object, and returns the object.

The problem we have is that helper was initialized twice, with different objects.

That's why the second test is necessary.

Upvotes: 7

Related Questions