One more look at Double-Checked Locking

28

I was talking with my friend at lunch yesterday about the good old days when double-checked locking was all the rage. We’re both in the midst of reading the totally excellent Java Concurrency in Practice by Brian Goetz, et al. (If you haven’t read it yet, go buy it today!)

Double-checked locking

To refresh your memory, double-checked locking was a classic technique (now considered broken, bad, and evil in its original form) to lazily construct a singleton while (usually) avoiding the performance hit of synchronization.

So, say you want only a single PieChucker in your high performance app. You might use double-checked locking like this:

// Old-school double-checked locking – THIS IS BROKEN!!!
public class PieChucker {
// Singleton instance, lazily initialized
private static PieChucker instance;

public static PieChucker getInstance() {
if(instance == null) {
synchronized(PieChucker.class) {
if(instance == null) {
instance = new PieChucker();
}
}
}
return instance;
}

// Private as it’s only constructed by getInstance()
private PieChucker() {
}

public void fling(Target target) {
// … chuck pie at target
}
}

My friend and I were trying to recall precisely why this is broken. We hand-waved around it for awhile but didn’t quite have it nailed, so I went home and looked it up (in the aforementioned book). I thought I would recap it here as this is a point that could serve to be hammered home until I learn it cold.

Happens-before

Consider this scenario: Thread 1 enters getInstance(), sees null on both if checks, and starts constructing the singleton. Thread 2 enters getInstance() and checks the first if check. At this point, thread 2 has not yet entered the synchronized block, so has not established a “happens-before” relationship with Thread 1. It is thus undefined whether Thread 2 will see a fully constructed instance (you got lucky!), a partially constructed instance (probably real bad), or null (causing two singletons to get constructed and breaking the singelton-ness).

So, what do you do instead? Actually, there are a variety of safe alternatives:

  1. Synchronized locking – lazy
  2. Double-checked locking with volatile – lazy
  3. Static initialization – eager
  4. Initialize on demand – lazy

Synchronized locking

One obvious change to the double-checked locking pattern to make it safe is to remove the first if-check and fall back to a fully-synchronized form of getInstance():

// Lazy synchronized locking
public class PieChucker {
// Singleton instance, lazily initialized
private static PieChucker instance;

public static PieChucker getInstance() {
synchronized(PieChucker.class) {
if(instance == null) {
instance = new PieChucker();
}
}
return instance;
}


}

This is guaranteed to be safe due to the synchronized block, which establishes a happens-before relationship. The down-side is that you must enter the synchronized block on every call to getInstance(). However, synchronization on modern JVMs is much faster than it was when double-checked locking was first popular.

Double-checked locking with volatile

If you want to avoid the synchronized block, you can actually fix double-checked locking by using volatile on the instance attribute. The getInstance() method requires no change.

private static volatile PieChucker instance;

Declaring instance volatile basically means that an update of the variable establishes a happens-before relationship. It’s kind of like having the readers and writers of the volatile being enclosed in synchronized blocks such that the reader is guaranteed to see the updated, safely published version written by the writer.

This form of double-checked locking is thread-safe and probably faster than the fully synchronized form, although the differences will be small and vary between platforms. Note: This is not guaranteed to work on JVMs prior to Java 5. Your mileage may vary. As of Java 5, the Java Memory Model was formalized and this behavior is guaranteed.

Static initialization

Another option is to give up the lazy instantiation of the singleton. In cases where singletons have little initialization state and are likely to be used, this will be better than any lazy strategy. So, PieChucker might look like this:

// Static initialization – eager
public class PieChucker {
// Singleton instance, eagerly initialized
private static PieChucker instance = new PieChucker();

public static PieChucker getInstance() {
return instance;
}


}

Static initialization is guaranteed to be thread-safe and no synchronization must be incurred. If your singleton is a candidate for eager initialization, I’d recommend using it as it’s so simple.

Initialize on demand

If you really want lazy initialization, we can do better! Because static classes are not initialized until needed (and the initialization is guaranteed to be thread-safe), the following idiom is safe and incurs no synchronization (plus, it’s shorter):

// Lazy initialization, no synchronization
public class PieChucker {
// Singleton class, lazy initialized
private static class InstanceHolder {
static PieChucker instance = new PieChucker();
}

public static PieChucker getInstance() {
return InstanceHolder.instance;
}


}

I’ve blogged before on one instance where this is used in the JVM itself. Integer.valueOf() lazily caches Integer objects from -128 to 127 (as required by the JVM specification). But, you don’t want to incur the cost of creating the cache unless it’s needed, so the cache is initialized on demand via a private static class.

I must credit Bob Lee with my first introduction to the idiom, although it is probably best documented in Josh Bloch’s Effective Java (#48). You might find this reference or this presentation to be of interest regarding the Java Memory Model, double-checked locking, happens-before, etc.

Comments are closed.