Java Double Locking - Can someone explain more sim

2020-06-06 07:34发布

问题:

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!

回答1:

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.



回答2:

You need to declare the field volatile because that will force the write to the field to be "flushed" to main memory. Otherwise the JVM specification allows each thread to keep its local version of the field and never communicate its writes to other threads. This is generally nice because it allows aggressive optimizations in the JVM.

Hope that helps! Else I can recommend getting a really strong cup of coffee, a very quiet room and then read the Java Memory Model which explains how it works and the interaction between threads. I think you will be surprised in how few situations a thread is required to communicate its writes to (shared) memory to other threads and the reordering of reads and writes that the JVM can perform!

An exciting read!