-1

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!

K2xL
  • 9,730
  • 18
  • 64
  • 101

2 Answers2

4

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.

Alexei Kaigorodov
  • 13,189
  • 1
  • 21
  • 38
  • question, and this might be a dumb one, but why doesn't java require all three steps to finish before having == null evaluate to false? – K2xL Sep 16 '12 at 17:56
  • because comparing to null is in different thread. If it were on the same thread, then you'll never see partially constructed object. – Alexei Kaigorodov Sep 16 '12 at 18:06
  • @K2xL Java's memory model says that the reordering of the assignment to the memory location and the initialisation is available as a performance optimisation to HotSpot, unless you tell it otherwise. You use safe-publication (volatile, final fields, whatever etc.) to tell it otherwise. This ability to optimise is important, if it was thread-safe by default it would also be a lot slower. – Jed Wesley-Smith Sep 17 '12 at 01:29
  • @JedWesley-Smith interesting, i wonder under what circumstances it would be faster to assign the memory location earlier... – K2xL Sep 17 '12 at 15:54
  • In addition to instruction reordering, the other classic problem with DCL (which happens in Java as well as other languages) is in multiprocessor systems, caching can cause changes done by one processor to be perceived as out of order on another. See cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html for a good full description of DCL issues in Java. – Jason C Aug 06 '13 at 03:48
1

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!

Nicholas
  • 2,147
  • 3
  • 23
  • 31
  • Not quite correct about volatile: 'synchronized' also flushes to main memory. You can never use volatile, only 'synchronized', and have correct programs. Also, partially constructed object can be seen, see reference in my answer. – Alexei Kaigorodov Sep 16 '12 at 17:42