3

I've read all about how double checked locking fixes never work and I don't like lazy initialization, but it would be nice to be able to fix legacy code and such a problem is too enticing not to try to solve.

Here is my example: private int timesSafelyGotten = 0; private Helper helper = null;

public getHelper()
{
    if (timesSafelyGotten < 1) {
        synchronized (this) {
            if (helper == null) {
                helper = new Helper();
            } else {
                timesSafelyGotten++;
            }
        }
    }
    return helper;
}

This way the synchronized code must run once to create the helper and once when it is gotten for the first time so theoretically timesSafelyGotten cannot be incremented until after the synchronized code which created the helper has released the lock and the helper must be finished initializing.

I see no problems, but it is so simple it seems too good to be true, what do you think?

Caleb James DeLisle

3
  • 6
    If you read (and understand) one of the detailed explanations of why double checked locking is broken, you'll understand why your code is also broken. Commented Dec 10, 2009 at 5:33
  • 1
    Its also worth noting that with Java 5 and volatile you can make unbroken double checked locking. Java 4 and earlier your statement is indeed true. Commented Dec 10, 2009 at 5:40
  • 2
    It's also worth noting that with J5 and greatly improved performance for uncontended locks DCL is almost always a premature optimization that is not worth the risk. Commented Dec 10, 2009 at 6:16

3 Answers 3

4

Without a memory barrier (synchronized, volatile, or equivalent from java.util.concurrent), a thread may see actions of another thread occur in a different order than they appear in source code.

Since there's no memory barrier on the read of timesSafelyGotten, it can appear to another thread that timesSafelyGotten is incremented before helper is assigned. That would result in returning null from the method.

In practice, this might work on many architectures during your unit tests. But it's not correct and will eventually fail somewhere.

Double-checked locking does work now, but it's tricky to implement correctly and fairly expensive. There are patterns for lazy initialization that are less fragile, more readable, and don't require anything exotic.

Sign up to request clarification or add additional context in comments.

3 Comments

Do you have a link to a good tutorial for lazy initialization patterns in the latest versions of Java?
Check out the "Initialization on Demand Holder" idiom: en.wikipedia.org/wiki/Initialization_on_demand_holder_idiom
I like IODH too - simple, lazy initialization, fast and no problems with synchronization.
2

If you are using JDK5+, use java.util.concurrent, in your case probably AtomicInteger.

These utilities are provided specifically because no one can be expected to understand the low-level thread synchronization primitives well enough to make them work properly.

Comments

1

That's not good. You can get timeSafelyGotten > 1. Example:

  1. Thread1 checks if successfully and stops on synchronization line
  2. Thread2 checks if successfully and stops on synchronization code.
  3. Thread3 checks if successfully and stops on synchronization code.
  4. Thread1 falls into sync block, creates helper and leaves this block.
  5. Thread2 falls into sync block, increment timeSafelyGotten and leaves this block.
  6. Thread3 falls into sync block, increment timeSafelyGotten and leaves this block.

So timeSafelyGotten = 2.

You should add one more check:

if (helper == null) {
    helper = new Helper();
} else if (timesSafelyGotten < 1) {
    timesSafelyGotten++;
}

or move sync upper:

synchronized(this) {
   if (timeSafelyGotten < 1) {
       ...
   }
}

The first way is better because it doesn't use sync every time.

One more hint: Don't use synchronize(this) because somebody can use your object for synchronization too. Use special private object for internal synchronization:

classs MyClass {
    private Object syncRoot = new Object();

    ...
    synchronized(syncRoot) {
        ....
    }
}

2 Comments

Thank you for the quick reply, are not threads 2 and 3 blocked out of the synchronized code (at step 5) until helper is finished initializing? If so then it works doesn't it? Also I like synchronized(syncRoot) idea.
Yes, they are blocked, but they are blocked after the first check and after unblocking they a fall into sync block. I've rewritten the example to make it more clear.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.