Search This Blog

Simplifying ReadWriteLock with Java 8 and lambdas

Considering legacy Java code, no matter where you look, Java 8 with lambda expressions can definitely improve quality and readability. Today let us look at ReadWriteLock and how we can make using it simpler. Suppose we have a class called Buffer that remembers last couple of messages in a queue, counting and discarding old ones. The implementation is quite straightforward:

Now we can putItem() many times, but the internal recent queue will only keep last capacity elements. However it also remembers how many items it had to discard to avoid memory leak. This class works fine, but only in single-threaded environment. We use not thread-safe ArrayDeque and non-synchronized int. While reading and writing to int is atomic, changes are not guaranteed to be visible in different threads. Also even if we use thread safe BlockingDeque together with AtomicInteger we are still in danger of race condition because those two variables aren't synchronized with each other.

One approach would be to synchronize all the methods, but that seems quite restrictive. Moreover we suspect that reads greatly outnumber writes. In such cases ReadWriteLock is a fantastic alternative. It actually consists of two locks - one for reading and one for writing. In reality they both compete for the same lock which can be obtained either by one writer or multiple readers at the same time. So we can have concurrent reads when no one is writing and only occasionally writer blocks all readers. Using synchronized will just always block all the others, no matter what they do. The sad part of ReadWriteLock is that it introduces a lot of boilerplate. You have to explicitly open a lock and remember to unlock() it in finally block. Our implementation becomes hard to read:

As you can see we wrap ReadWriteLock and provide a set of utility methods to work with. In principle we would like to pass a Runnable or Supplier<T> (interface having single T get() method) and make sure calling it is surrounded with proper lock. We could write the exact same wrapper class without lambdas, but having them greatly simplifies client code:

See how we invoke guard.read() and guard.write() passing pieces of code that should be guarded? Looks quite neat. BTW have you noticed how we can turn any collection into any other collection (here: Deque into List) using stream()? Now if we extract couple of internal methods we can use method references to even further simplify lambdas:

This is just one of the many ways you can improve existing code and libraries by taking advantage of lambda expressions. We should be really happy that they finally made their way into Java language - while being already present in dozens of other JVM languages.