Composing Objects – Java Concurrency in Practice

Hi, Java dudes! In my previous post, Sharing Objects – Java Concurrency in Practice, I reviewed how objects can be published and accessed from multiple threads in a safe manner. Today’s topic is how thread-safe components can be combined and enriched with new functionality.

While it is possible to write a thread-safe application that stores all its state in public static fields, but it is much easier if we do it by combining thread-safe components. By doing so we can delegate every thread-safety issue to the right application tier where it needs to be handled. Let the thread-safe class deal with how access to its inner state has to be synchronized. While on a higher level, we can think about how these thread-safe components can be used to represent the application state properly.

In order to prevent concurrency issues, we have to think about state ownership. This is something that is not part of the language itself but it is defined by class design. Therefore programmers have a great autonomy to make the right choices – and the bad ones too. A class usually does not own the objects passed to its methods. In many cases, ownership and encapsulation go together – the object encapsulates the state it owns and owns the state it encapsulates. Collections often share ownership of the contained objects with the client code that inserted them into the collection.

The Java monitor pattern is an example for ownership by encapsulation.

An object following the Java monitor pattern encapsulates all its mutable state and guards it with the object’s own intrinsic lock [JCiP]

Synchronized collections offered by the standard JDK follow the Java monitor pattern. Synchronization is implemented as a wrapper layer that controls all access to the underlying collection. A synchronized list of Strings can be created like this:

import java.util.Collections;
...
List syncStrings = Collections.synchronizedList(new ArrayList());

It is critical that the reference to the backing collection must not escape otherwise it can undermine thread-safety. Operations over a synchronized collection are mutually exclusive – only one thread at a time can work with the collection. In most cases, this is too restrictive and result in poor application performance. There are other alternatives but more on that later.

Now let’s suppose we have a class that is already thread-safe but misses an operation that we need. What can we do in such a situation? We have multiple options, but not all of them are correct:

  • put new synchronized code in a helper class, ☠
  • extend the original class, ☠
  • modify the original class to support the desired operation, or
  • add new functionality in a class encapsulating the original one.

Using a synchronized method of a helper class is probably the worst possible approach. Whatever lock the parameter object uses it definitely won’t be the intrinsic lock of the helper method. It only gives us the illusion of safety while other threads can still modify the state of the passed in object.

Extending the original class – if possible – could work, but it’s fragile because the underlying class might silently change its synchronization policy and jeopardize thread-safety of the extending class. Another problem is that not all classes expose enough of their state to make this approach possible (private lock).

The safest way to add a new atomic operation is to modify the original class consistently with its original design. In this case, all code that implements the synchronization policy is contained in a single source file – easier to understand and maintain.

If changing the existing class is not an option then using composition is the best alternative. This is how standard synchronized collections are implemented. And just as with synchronized collections, it’s crucial that all access to the underlying object must go through the wrapper class.

Don’t force clients or other developers to make risky guesses about the thread-safety of your code.

Document a class’s thread safety guarantees for its clients; document its synchronization policy for its maintainers. [JCiP]

Did you know that java.text.SimpleDateFormat is not thread-safe? Okay, maybe you did, but did you know that it wasn’t explicitly documented until JDK 1.4? And this is the worst that can happen: you assume that something is thread-safe that is not. This is why you should always document your code.

...
@ThreadSafe
public class ImprovedList<T> {

    @GuardedBy("this")
    private final List<T> innerList;

    public ImprovedList(List<T> list) {
        this.innerList = list;
    }

    public synchronized void putIfAbsent(T elem) {
        if (!innerList.contains(elem)) {
            innerList.add(elem);
        }
    }
}

Annotations @ThreadSafe and @GuardedBy are not part of the standard JDK.

That was all for now. Next week we are going to look at what building blocks Java provides that we can use as Lego™ pieces to build our application. Best wishes until we meet again here 😉

P.S.: instead of java.text.SimpleDateFormat use its thread-safe Java 8 alternative the java.time.format.DateTimeFormatter – where applicable.

Resource
[JCiP] Java Concurrency in Practice by Brian Goetz, ISBN-10: 0321349601

Leave a Reply

Your email address will not be published. Required fields are marked *