Optimizing Thread Safety in Java
There is an obvious trend in recent years in the hardware industry. It seems as though processors are not getting faster, but rather manufacturers are trying to pack as many cores as possible onto the same chip. As a result multi-threaded software has become more and more desired.
The synchronized keyword is the defacto standard for implementing thread safety in Java. However, as of Java 5 and the introduction of the java.util.concurrent package, there are a few more options for Java developers. For a locking solution there’s the Lock interface and for a non-locking solution there’s a set of Atomic classes.
I was wondering why there was need for these new classes so I decided to try them out for myself!
And now for a contrived example. Let’s say we want to have a set of threads wanting to get numbers from a global sequence. So first, we need some sort of sequencing singleton:
Since we have a singleton that’s going to be hit up by a lot of threads we have to be really careful with any sort of state. The above would be the standard implementation for making sure the sequence is incremented by only one thread at a time.
A simple way to test the throughput of this method is the time the longest-running thread takes to complete. We can use the following class to do this:
With the standard implementation, the longest running thread took 150ms.
Now for the locking implementation using the new java.util.concurrent.locks.Lock interface:
This time the longest running thread took 122ms. This is a relatively minor improvement but perhaps with a more complex system it may be more noticeable.
Let’s try out the AtomicLong class (which admittedly suits this contrived example extraordinarily well) to solve the thread safety issue:
This time the longest running thread takes only 16ms!!. This is an improvement of an order of magnitude over the synchronized keyword.
The reason for this massive improvement is (as Brian Goetz describes here) because they are implemented in a wait-free fashion (i.e. lock-free), using a native implementation not available on the JVM.
There is a set of classes implemented with these atomic variables that give you built-in optimization for much better performance. As you can see, it may be worth trying them out next time you need to do some threading.