4 Simple Rules for Effective Bug-free Concurrent Programming

This article shows with an example some rules for effective concurrent programming

Introduction

Writing correct programs is hard, but writing correct concurrent programs is far harder. Despite that, a Java developer (at any competence level) can't just ignore the problem. And in the very last year, there’s another good reason to take care even more of multithreading programming: the race towards always higher CPU frequency and number of transistors integrated on a single core is going to finish because the CPU makers are starting to face some physical limitations with the request of less power demanding devices. Today it is already possible to find lots of (even low-end) devices having two or more cores.

For these reason, programs are moving from being concurrent, serving more requests at the same time, to being parallel, having the possibility to split the execution of a complex time-consuming request in more subtasks that can run together in order to take advantage of multi-processor or multi-core systems. This scenario brings new (stimulating) challenges on the desktop of each Java developer and each one of us should try to acquire the necessary skills to win these challenges.

The Base Example

The purpose of this article is to mention and illustrate some simple but very important rules for writing effective and bug-free Java multithreaded programs. The rationale behind those rules will be justified by analyzing and improving this very simple Java class:

Stateless is Thread-safe

This class has only a single public method that calculates the factorial of the given BigInteger parameter. What happens if we share an instance of this class through different threads? Is that instance thread-safe? Of course, the answer is yes and for a very simple reason: one thread accessing a FactorialCalculator cannot influence the result of another thread accessing the same object, because the two threads do not share state. This is probably the easiest to understand and at the same time most important rule of multithreaded programming: stateless objects are always thread-safe.

Modify State Consistently

Of course to have objects with some sort of state is often just unavoidable. In order to add a little state to the first example, let's make it just a bit more complicated by adding a variable that counts the number of times the factorialOf() method is called:

What about thread-safety now? Since our FactorialCalculator is no longer a stateless object, we have to pay a bit more attention to what could happen during a multithreaded execution. Actually while the increment operation (++count) may look like a single action because of its compact syntax, it is not atomic, which means that it does not execute as a single, indivisible operation. Instead, it is a shorthand for a sequence of three discrete operations: fetch the current value, add one to it, and write the new value back. Even worse, in this example, we could not only read a stale value, but also a pseudo-random value that has not been written by any thread. That’s why the Java Memory Model requires fetch and store operations to be atomic, but for nonvolatile long and double variables, the JVM is permitted to treat a 64-bit read or write as two separate 32-bit operations. These considerations make it evident that our object is now susceptible to what is called a lost update and that its thread-safety is definitively broken. This problem put in evidence the second simple rule for a bug-free multithreaded program: always check for atomicity.

Know Your Library

To fix the lack of atomicity problem in our simple code is quite easy. Starting from Java 5.0, the java.util.concurrent.atomic package contains atomic variable classes for effecting atomic state transitions on numbers and object references. By replacing the long counter with an AtomicLong, we ensure that all actions that access the counter state are atomic:

Atomic variable classes are just one of the many improvements offered by Java 5 over the previous versions that allow to make concurrent development easier. Other fundamental features provided by Java 5 are a new set of thread-safe collections, an executor framework for running unrelated tasks and several other kinds of classes that have been added to better support advanced concurrent design like Semaphore, ReentrantLock and CountDownLatch. A good knowledge of these classes and their APIs allow to build our concurrent programs on a set of well-proven thread-safe libraries without the need to reinvent the wheel.

Preserve Invariants

Now suppose we want to improve the FactorialCalculator even a bit more. In particular, we discovered that for some reason the clients of our object tend to ask the factorial of a given number more than once quite often, so we decided to improve its performance by caching the last computed factorial value as it follows:

We update the values of lastNumber and lastFactorial via AtomicReferences so the object’s thread safety is still guaranteed, isn't it? Unfortunately no. This approach does not work, because even though the atomic references are individually thread-safe, FactorialCalculator still has a race condition that could make it produce the wrong answer.

An invariant is a condition that, in order to preserve the correctness of a multi-threaded program, needs to be verified immediately before and after each atomic operation performed by the program itself. The only invariant of FactorialCalculator is that the factorial value cached in lastFactorial has to correspond to the value cached in lastNumber and it is correct only if this invariant always holds. What is said implies that the third rule to keep bugs out of our parallel programs is: ensure that invariants are preserved regardless of timing or interleaving of operations in multiple threads.

Use Small Synchronized Blocks

As stated, in order to preserve the FactorialCalculator invariant, we need to keep synchronized the code that modifies its state as it follows:

This last version of our class enforces the invariant of the last cached result by guarding the read and write operations on lastNumber and lastFactorial with the same lock. Note that we could achieve the same result in an easier (and more readable way) by synchronizing the whole factorialOf() method, but this poor choice implies that all the invocations to that method will be completely serialized, making useless our effort to write a thread safe class that can perform well in a multithreaded environment. From this last consideration derives the fourth rule: keep synchronized blocks as small as possible (but not smaller) in order to not excessively compromise the parallel execution of our code.

Comments and Discussions

I wonder how the two small synchronized blocks would affect performance, as opposed to one big synchronized block. I think that the latter would have better performance in your specific example, due to the overhead associated with obtaining and releasing locks.

Additionally, locking over this may not be a good idea. For some relevant information, check out this article[^] or that one[^].

The overhead associated with obtaining and releasing locks in Java is very low and actually I am not sure whether in my specific example the performance gain obtained by avoiding the double synchronization is bigger then keep out the calculation method outside the synchronized blocks.

I suppose there isn't a general answer to that question and it depends on how many threads are accessing the method in parallel and how long it takes the unsychornized calculation method.

Anyway you should take my example as it is: just an example. Its purpose was only to give a suggestion or to show a possible solution for a problem. Just suppose that the method left outside the synchronized block needs to read an XML file from the file system and generate the corresponding DOM. Are you still sure that to put it in a synchronized block in order to avoid the overhead associated with the lock management is a good idea?

As for the advice to avoid the synchronization over the this object, the 2 articles you linked are referred to C# and I am not sure that the Java Memory Model is affected by the same issues. Actually I did a quick search to discover if there were articles that speak about the same problem in Java and I couldn't find any.

Anyway I agree that to synchronize on a private object instead of on this could ensure better encapsulation and help to avoid the possibility of a deadlock, so this could be the 5th simple rule of the article.

I don't know Java, but if I'm reading the code and understanding the example correctly, by having one synchronized block around the contents of the entire method would completely block any other threads from even entering the method and beginning the computation. Therefore, in a highly concurrent system, your performance would actually decrease.

So you can add another general rule of thumb to this article if you'd like and that is to keep any synchronized blocks (or Critical Sections in C++) as small as possible.

There is an application here at my work, written in Java, in which just about every method is marked as synchronized. It's horrible and the program suffers from deadlock often.