Why Double-Checked Locking is STILL Broken

Paul Bares
3 min readAug 16, 2020

--

This story is a follow-up of this article https://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html (DCL) signed by famous computer scientists and demonstrates why most of the code written today in Java that applies the Lazy Initialization pattern is wrong. I give three points to explain why I think it’s still broken in today’s programs.

As reminder, the pattern is made for delaying the creation of an object only when it is needed. In a single threaded program, this is very simple:

class Foo {   private Object obj = null;  public Object get() {
if (this.obj == null) {
this.obj = new Object();
}
return this.obj;
}
}

But in a multi-threaded program, that’s a different story. I highly encourage you to read thoroughly the article linked above to understand the whys and wherefores.

So why this code in most of programs is still wrong?

Java Memory Model is tough

As Aleksey Shipilëv stated:

The Java Memory Model is the most complicated part of Java spec that must be understood by at least library and runtime developers.

I notice that only few Java developers know there is a Java Memory Model and only few of them know what it is. Today, the demand of Full-Stack developers is increasing due to the businesses expanding rapid pace. Specialized developers tend to be a rare commodity. By definition, these developers have basic knowledge of every front-end, back-end and database framework/language so having to deal with the Java Memory Model is the last thing they want to do.

Multi-threading issues are difficult to detect and test

Spotting a concurrency issue can be tricky especially if the code is convoluted and not well documented. There are several options to prevent concurrency issues.

Option 1: code reviews. Some of senior guys developed the ability to rapidly identify a race condition when reading code but it requires their full attention or it can quickly fall through the cracks. In the case of DCL, if the class contains too many lines, it becomes easy for the reviewer to forget to check if the attribute has been declared as volatile.

Option 2 is catching bugs by testing. But let’s be honest, testing multi-threaded code is difficult, takes a lot (lot) of time and concurrency issues are intermittent so that it becomes hard to make sure a particular test tests the problem we are trying to solve.

x86

The partial initialization of the lazy instantiated object cannot be catched with all CPU architectures! Most of the servers and personal computers in the world use x86 CPU architecture which has a TSO (Total Store Ordering) memory model (https://en.wikipedia.org/wiki/Memory_ordering) so it means that the DCL issue mentioned in the article never happens for code running on a x86 CPU architecture!

Conclusion

If you are not sure about a pattern you are using especially in a multi-threaded environment, start simple: follow the KISS principle. Meaning in the lazy initialization case: don’t lazy initialized.

class Foo {
private final Object obj = new Object();
}

You should wonder whether the lazy initialization is really worth the effort and additional lines of code or if the object can be instantiated “normally”. If true, do it else, do some research before https://stackoverflow.com/search?q=double+checked+lockin and understand after.

--

--

Paul Bares
Paul Bares

Written by Paul Bares

I'm an enthusiast in computer hardware and programming. I specialize in high performance and parallel computing. Co-Creator of SquashQL Github: squashql

No responses yet