Implementation

A basic assumption of this pattern is that the test for whether or not initialization of the resource has occurred will not be true until the initialization is finished. If it is possible for it to be true when the initialization is only partially complete, then another thread may execute the outer if and skip past the synchronized statement although the initialization is not complete. There are subtle details of Java that can give rise to this situation, although the code appears correct.

Consider the following listing:

    long foo;
    ...
    void doIt() {
        if (foo==0) {
            synchronized (this) {
                if (foo==0) {
                    foo = System.currentTimeMillis();
                } // if
            } // synchronized
        } // if
        ready()
    }

The code looks right, as far as it goes. However, it is possible for a thread to call the ready method before the assignment to foo is complete. The root of the problem is the way that Java handles assignments to long and double variables. Unless they are declared volatile, implementations of Java are allowed to treat assignments to long and double variables as two assignments to two variables that hold 32 bits each rather than one assignment to single variable that holds 64 bits. In other words, implementations of Java are allowed to implement assignments to long or double variables as two separate operations.

If the code in question is running in an environment that implements assignments to long as two operations, it is possible for a thread to test for foo equal to zero when only half of foo has been assigned. This can produce incorrect results.

You can force the code to have the intended behavior by declaring foo to be volatile, like this:

    volatile long foo;

However, a compiler is likely to implement the semantics of volatile by implicitly generating a synchronized statement around each place that the variable is accessed. This defeats the purpose of the Double-Checked Locking pattern, which is to avoid unnecessary synchronization delays. I do not recommend using the Double-Checked Locking pattern for the initialization of long or double variables, unless you know that your program will be running only in environments that treat assignments to long or double as a single operation.

There is another subtlety of Java that can cause a similar problem. The Java language specification allows an implementation of Java to execute operations in a somewhat different order than specified by source code. The actual rules are a bit complicated; however they guarantee that the thread the executes the operation will execute as if the operations were being executed in the specified order. In particular, the rules make if possible for a value to be assigned to a variable as soon as it becomes inevitable that the assignment will be made, unless the variable is declared volatile. For example, consider the statement

        x = new Bar();

The language specification allows the assignment to x to be done before the Bar object’s constructor has returned. The reason that the language specification allows this sort of reordering of operations is to allow compilers to perform some optimizations.

Theoretically, this is a very serious problem that can break many applications of the Double-Checked Locking pattern. In practice, it is not a serious problem at all.

The reason it is not a serious problem is that compiler writers know this sort of optimization can break programs. If the default setting for a compiler caused it to perform optimizations that break programs, not many people would want to use the compiler. As a practical matter, a compiler will not perform unsafe operation on programs unless you explicitly set options for it to do so. The real implication of this is that if you use the Double-Checked Locking pattern, be careful about what optimization you enable.