haoyu wang
haoyu wang

Reputation: 1377

why java double check lock singleton must use the volatile keyword?

I have seen some similar questions,but I still have some confusions.
code is here:

private volatile static DoubleCheckSingleton instance;

private DoubleCheckSingleton() {}
public static DoubleCheckSingleton getInstance(){

    if(instance==null){ //first

        synchronized (DoubleCheckSingleton.class){

            if(instance==null){  // second
                instance=new DoubleCheckSingleton();
            }

        }

    }

    return instance;

}

In this question Why is volatile used in double checked locking, it says that without a volatile keyword, a thread may assign the instance variable before the constructor finishes, so another thread may see a half-constructed object, which can cause serious problem.

But I don't understand how volatile can solve the problem. Volatile is used to ensure visibility, so when thread A assign a half-constructed object to instance variable, the other thread can immediately see the change, which makes the situation worse.

How does volatile solve the problem, somebody please explain to me. Thanks!

Upvotes: 1

Views: 940

Answers (3)

James Gawron
James Gawron

Reputation: 969

Double checked locking spent a good bit of time as an anti-pattern, according to Java Concurrency In Practice et. al. At the time they suggested using something like the Lazy Holder pattern instead.

https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom

Brian Goetz wrote a good article on how DCL was broken here https://www.javaworld.com/article/2074979/double-checked-locking--clever--but-broken.html

As StevenC pointed out to me, since the update to the Java Memory Model it will work properly, although in spite of this I think the Lazy Holder is still a nice alternative. It is clean to implement, and avoids the need of the volatile. Improper implementation of DCL will cause hard to find bugs.

Upvotes: 2

Mike Robinson
Mike Robinson

Reputation: 8945

To put it another way, suppose we have two perfectly-racing threads:

  • THREAD 1: "if instance != null" ... okay, it's null.
  • THREAD 2: "if instance != null" ... okay, it's null.
  • THREAD 1: creates a new "instance"
  • THREAD 2: STOMPS ON the value just created by Thread 1.

... and, quite likely, neither thread is presently aware that something is wrong. It's likely that both of them think that they have a DoubleCheckSingleton and are using them just as they should be ... neither of them realizing that there are two. (So, sit back and watch data-structures getting trashed left and right as your app nose-dives into the ground.)

Even worse, this is exactly the kind of bug that "happens once-in-a-blue-moon." Which is to say that it never happens on any developer machine, but it happens five minutes after you deploy to production ... ;-)

The volatile keyword basically says that "this storage location, itself is a shared resource." Which it is.

Now also: the best practice, I think, is to initialize all of your synchronization objects before you start to launch threads or subprocesses. All of these entities can now refer to these now already-existing objects and can use them, but none of these players ever create them.

Upvotes: -2

Solomon Slow
Solomon Slow

Reputation: 27115

a thread may assign the instance variable before the constuctor finishes

That's not actually true. The assignment is right there in the code example:

instance=new DoubleCheckSingleton()

Obviously, the thread that performs that assignment can not possibly do the assignment before the constructor call has returned.

The problem is, when two different threads are running on two different processors without any synchronization, they will not necessarily agree upon the order in which assignments happen. So, even though thread A assigned the fields of the new object (inside the new DoubleCheckSingleton() call) before it assigned instance, thread B potentially could see those assignments out-of-order. Thread B could see the assignment to instance before it sees some of the other things that new DobuleCheckSingleton() did.

Declaring instance to be volatile synchronizes the threads. volatile guarantees that everything thread A did before it assigned a volatile variable will become visible to thread B when thread B fetches the value of the volatile variable.

Upvotes: 4

Related Questions