Search code examples
javamultithreadingconcurrencyvolatile

Java Double Locking - Can someone explain more simply why intuition wouldn't work?


I found the following code here: http://en.wikipedia.org/wiki/Double-checked_locking#Usage_in_Java

I am trying to understand why there are certain cases where this would not work. I read the explanation of the "subtle" problems, and that using volatile will fix the issue, but I'm a bit confused.

// Broken multithreaded version
// "Double-Checked Locking" idiom
class Foo {
    private Helper helper = null;
    public Helper getHelper() {
        if (helper == null) {
            synchronized(this) {
                if (helper == null) {
                    helper = new Helper();
                }
            }
        }
        return helper;
    }

    // other functions and members...
}

Basically, am I right to assume this would fail due to the fact that the helper == null check in the synchronized block has a chance to fail because it could be "partially" constructed at that point? Does java not return null if an object is partially constructed? Is that the issue?

Anyway, I know that it's not great practice to do double check locking, but I was just curious in theory why the above code fails, and why volatile (plus the addition of assigning a local variable) fixes this? Here's some code I got from somewhere.

// Double-check idiom for lazy initialization of instance fields
private volatile FieldType field;
FieldType getField() {
    FieldType result = field;
    if (result == null) { // First check (no locking)
        synchronized(this) {
            result = field;
            if (result == null) // Second check (with locking)
                field = result = computeFieldValue();
        }
    }
    return result;
}

I know there are a thousand posts already about this, but explanations seem to mention changes in memory model after 1.5, and I don't quite get what that has to do with it too :-(.

Thanks in advanced!


Solution

  • am I right to assume this would fail due to the fact that the helper == null check in the synchronized block has a chance to fail because it could be "partially" constructed at that point?

    Yes you are right. This is explained in Out-of-order writes. helper = new Helper() consists of 3 steps: memory allocation, call to the constructor, and assignment. JIT compiler is free to reorder instructions and do assignment after memory allocation (which returns reference to the new object) but before the constructor invocation. Using volatile prevents reordering.