Java Patterns For Concurrency

This post talks about some of the patterns we can use to solve concurrency issues relating to state shared across multiple threads. The goal is to provide some smarter alternatives to slapping synchronized on every method call, or around every block of code. The problem with synchronized is that is requires everyone to participate. If you have an object that is mutable and it is shared by synchronizing on it, then nearly every use of that object will require a synchronized block and it only takes one person to forget to create bedlam. In general, the goal is to write code such that we don’t need to consider concurrency, and we can write code without concerning ourselves with how everyone else it handling concurrency.

Limit State Changes To A Single Thread

This is a pattern that has been used in Swing and also JMonkey Engine (JME) where changes to common state should only be made in a main thread. This pattern is useful when you have tasks that go off and run, and once complete they need to catch up and update the display or game objects in the case of JME. I put this one first as it is a very top level pattern. If you decide to go this route, then you don’t have to worry to much about concurrency as long as you always follow the rule of updating state on the main thread.

In any component that runs asynchronously, once completed, it can schedule a piece of code to run on the main thread. Swing and JME offer their own ways of doing this, but you could create your own that allows something like the following:

  public void execute() {
    int value = someLongProcessRunAsynchronously();
    MainThread.enqueue(() -> form.updateLabel(value+" records Found"));
  }

The Runnable code will be pulled from the queue by the main thread and executed. Obviously, any code run off the queue must be brief and not trigger any long running synchronous actions.

Duplicate State to Make it Local

We can duplicate mutable state into a local variable and reference it locally to take advantage of the fact that locally scoped data is inherently thread safe.


  //don't move or update if -ve
  if (sharedInt >= 0) {
    moveBy(sharedInt);
    update();
  }

The problem with this code is that if another thread interrupts it mid-execution and sets the sharedInt value to a negative number, it will have undesired results.

If we copy the value and then work only with our local copy we are guaranteed to have the same value each time it is referenced in our function.


  //don't move or update if -ve
  int localInt = sharedInt;
  if (localInt >= 0) {
    moveBy(localInt);
    update();
  }

Here, we have localised the state so it cannot be touched by other threads, but the downside of this is that it can be tricky and/or expensive to copy more complex objects since we must copy the object atomically. For this reason we might need the help of some other patterns to ensure that can happen.

Encapsulate State

Let us say we have a map of key/value pairs, from a functional point of view, we probably might just expose the map to the world and let our threads have at it.

public class MyState {

  private Map<String,String> parameters;

  public Map<String,String> getParameters() {
    return parameters;
  }
}

//client code 
 
   myState.getParameters().put("key","value");
   if (myState.getParameters().contains("this")) {
     //assume 'that' is also present
     callFunction(myState.getParameters().get("that");
   }

This opens a host of problems since any thread can get the map, keep references to it, and update it any time. This is the least thread safe thing we can do as our state has escaped our class.

First off, lets realise that just because we use a map to store some data, we don’t actually need to provide all access to the map to clients. In the interest of making it thread safe, we can encapsulate the map and just provide methods to access that map. Our state has no longer leaked out of the class and is somewhat protected. Once encapsulated, we can control how we access the map and do so in a manner that is more thread-safe.

For a first draft, we can simply make access methods synchronized :


public class MyState {

  private Map<String,String> parameters;

  public synchronized void addParameter(String key, String value) {
    parameters.put(key,value);
  }

  public synchronized String getParameter(String key) {
    return parameters.get(key);
  }
}

We’ve made our code a little safer and arguably, a little better according to the Law of Demeter.

One problem is we have put synchronized on the methods and if we have other synchronized methods in the same class, even for different reasons, they will end up blocking each other unnecessarily. One solution is just to synchronize on the parameters object when we use it which should improve things a little, but there are some alternatives we can use to make things even better:

Read/Write Locks

If we read from an object far more than we write, we could improve the above example further. We can use Read/Write locks to ensure that we only block read access to the object when we are writing, but otherwise we can concurrently read from the object without being blocked by other readers. Infrequently writing means that readers should not be blocked that often.


public class MyState {

  private Map<String,String> parameters;
  private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

  public void addParameter(String key, String value) {
    lock.writeLock().lock();
    try { 
      parameters.put(key,value);
    } finally {
      lock.writeLock().unlock();
    }
  }

  public String getParameter(String key) {
    lock.readLock().lock();
    try { 
      return parameters.get(key);
    } finally {
      lock.readLock().unlock();
    }
  }
}

This does take more code including the try/finally blocks but it allows nearly unlimited concurrent reading with only writes blocking. We also have some options for dealing with cloning state atomically which was an issue raised above. (As an aside, why do we not have a method for passing the lockable code as a lambda such as lock.readLock().execute(()->parameters.get(key));?).

We also have some nice options with locks that don’t make much sense here, but we can have timeouts for locks etc and as far as the interface goes, we can implement whatever helper functions we want and use the locks to handle them easily, such as the getter and add functions.

As an example, we can easily make a copy of the map without worrying about a write action disrupting things mid-execution:


  public Map<String,String> getParameters() {
    lock.readLock().lock();
    try { 
      return new HashMap<>(parameters);
    } finally {
      lock.readLock().unlock();
    }
  }

This means we can handle making a local copy of the state atomically and use it in conjunction with the local duplicate state pattern. This provides an implementation that is thread safe without the client even having to consider thread safety, nor can it be used in a thread unsafe manner.

Summary

These are some of the patterns you can use to defuse any concurrency issues with mutable state without having to resort to putting synchonized on everything and hoping for the best.

Comments are closed.