Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is this double-checked lock implemented with a separate wrapper class?

When I was reading Wikipedias's article about Double Checked Locking idiom, I'm confused about its implementation:

public class FinalWrapper<T> {
    public final T value;
    public FinalWrapper(T value) { 
        this.value = value; 
    }
} 
public class Foo {
    private FinalWrapper<Helper> helperWrapper = null;

    public Helper getHelper() {
        FinalWrapper<Helper> wrapper = helperWrapper;

        if (wrapper == null) {
            synchronized(this) {
                if (helperWrapper == null) {
                    helperWrapper = new FinalWrapper<Helper>(new Helper());
                }
                wrapper = helperWrapper;
            }
        }
        return wrapper.value;
    }
}

I simply don't understand why we need to create wrapper. Isn't this enough ?

if (helperWrapper == null) {
    synchronized(this) {
        if (helperWrapper == null) {
            helperWrapper = new FinalWrapper<Helper>(new Helper());
        }
    }
}    

Is it because using wrapper can speed up initialization because wrapper is stored on stack and helperWrapper is stored in heap?

like image 222
Audrey Avatar asked Jan 24 '12 06:01

Audrey


People also ask

Why double-checked locking?

In multi-threaded environments, initialization is usually not thread safe, so locking is required to protect the critical section. Since only the first access requires locking, double-checked locking is used to avoid locking overhead of subsequent accesses.

Is double-checked locking good?

Even though the double-checked locking can potentially speed things up, it has at least two issues: since it requires the volatile keyword to work properly, it's not compatible with Java 1.4 and lower versions. it's quite verbose and it makes the code difficult to read.

Can double-checked locking fail?

No matter how you rig it, double-checked locking still fails Unfortunately, DCL isn't guaranteed to work under the current Java Memory Model (JMM.)


1 Answers

Simply using helperWrapper for both null checks and the return statement could fail due to read reordering allowed under the Java Memory Model.

Here is the example scenario:

  1. The first helperWrapper == null (racy read) test evaluates to false, i.e. helperWrapper is not null.
  2. The final line,return helperWrapper.value (racy read) results in a NullPointerException, i.e. helperWrapper is null

How did that happen? The Java Memory Model allows those two racy reads to be reordered, because there was no barrier prior to the read i.e. no "happens-before" relationship. (See String.hashCode example)

Notice that before you can read helperWrapper.value, you must implicitly read the helperWrapper reference itself. So the guarantees provided by final semantics that helperWrapper is fully instantiated do not apply, because they only apply when helperWrapper is not null.

like image 145
Patrick Parker Avatar answered Sep 23 '22 06:09

Patrick Parker