Skip to main content

Rechecking Double Checking

Posted by mason on September 11, 2006 at 11:06 AM PDT

If you are new to the concept, here's a refresher. Or if you prefer the layman's version:

Let's say you want to have a lazily initialized singleton. Something like:

public class Foo {

    private static Foo _foo = null;
   
    public static Foo getInstance() {
        if (_foo = null) {
            _foo = new Foo();
        }
       
        return _foo;     
    }
}

All good and dandy... So, what's the problem with this once your code becomes multi-threaded? Ah, of course, it's possible for it to get initialized twice... Easy to fix, right? Just add a synchronized block after the if statement, and then check again after you have synchronized to make sure the object still needs to be initialized, a classic "double checked lock":

        if (_foo = null) {
            synchronized (Foo.class) {
                if (_foo = null) {
                    _foo = new Foo();
                }
            }
        }

And all is well.... right?

Unfortunately not. Synchronized, the keyword, does not mean what you think it means, in particular in relation to Java's memory model. Because the Hotspot compiler is so "smart" it's quite possible that, while things INSIDE of the synchronized block will not conflict, things that are OUTSIDE of the block can be run "out of order". As in, it's possible on one thread that the "return _foo" statement will happen while the synchronized block is in a holding pattern, waiting for a different thread to initialize _foo. Since _foo is being returned before it is properly initialized, it will lead to all manner of oddness and, frankly, it's better not to try and think too hard about it.

The solution came in 1.5, specifically the wonderful new "java.util.concurrent.locks" package. These locks do NOT, in fact, seem to use synchronization to do their business (correct me if I am wrong, this comes from scrutinizing the code, but I could have missed something), instead they use some fancy thread scheduling magic (the locked threads are simply paused, and just never scheduled to run until the lock is released. Not sure if this technique is new or not, but it is very clever in my book).

The new locks are safe from the woes of synchronization (The Javadoc for the ReentrantReadWriteLock, for instance, uses a double checked code snippet as an example of proper use!) and make the entire process work as planned:

ReadWriteLock lock = new ReentrantreadWriteLock();

try {
    lock.readLock().lock();
    if (_foo == null) {
        lock.readLock().unlock();
        lock.writeLock().lock();
        try {
            if (_foo == null) {
                _foo = new Foo();
            }
        } finally {
            lock.readLock().lock();
            lock.writeLock().unlock();
        }
    }
} finally {
    lock.readLock().unlock();
}

And that is all there is to it. It's a little bit ugly, though, and easy to screw up. Using a little code-wrapping technique I discussed in my last post, this complexity can be simplified by wrapping this logic in a utility class:

public abstract class DoubleCheck extends ReentrantReadWriteLock {

    protected abstract R create();
    protected abstract R retrieve();
    protected abstract boolean invalid(R value);

    public R get() {
        try {
            readLock().lock();
            R value = retrieve();
            if (invalid(value)) {
                readLock().unlock();
                writeLock().lock();
                value = retrieve();
                try {
                    if (invalid(value)) {
                        value = create();
                    }
                } finally {
                    readLock().lock();
                    writeLock().unlock();
                }
            }
            return value;
        } finally {
            readLock().unlock();
        }
    }
}

This little class can simply be overridden by anything that wants to perform a double checked lock. For example, if you want to create a simple lazy object singleton, you might do something like this:

public abstract class Singleton extends DoubleCheck{
    private X _instance = null;
    protected X retrieve() { return _instance; }
    protected boolean invalid(X value) { return _instance == null; }
}

Now you can use it to create any sort of singleton object that you want in a perfectly thread-safe manner:

Singleton randomPort = new Singleton() {
    protected Integer create() {
        return new Random().nextInt(50000) + 1024;
    }
}

System.out.println("Random port for this JVM instance is: " + randomPort.get());
System.out.println("As I said, the random port is: " + randomPort.get());

Play with it a bit and see if it's something that comes in useful for you.

Related Topics >>