Java Patterns for Concurrency
Want to learn more about Java patterns for concurrency? Check out this post to learn more about how to solve concurrency issues with multiple threads.
Join the DZone community and get the full member experience.
Join For FreeThis post talks about some of the patterns we use to solve concurrency issues relating to the 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 it requires everyone to participate. If you have an object that is mutable and 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 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 JMonkey Engine (JME) where changes to the common state should only be made in the 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 about concurrency in the project 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 to allow 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 Is Local
We can duplicate mutable state into a local variable and reference it locally to take advantage 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 shared int value to a negative number, it will have undesired results.
If we copy the value and then work only with our local copy, then we are guaranteed to have the same version 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 localized the state so it cannot be touched by other threads, but the downside of this is that it can be tricky with more complex objects since we must copy the object atomically. Even working with the built-in collections can be tricky. For this reason, we might need the help of some other patterns to ensure that can happen.
Encapsulate State
If we have a map of key-value pairs, we will probably 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.
First off, let's realize 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 safer and, arguably, a little better according to the Law of Demeter.
One problem is that we have put synchronized on the method, 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. Let's take a closer look:
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 block read access to the object when we are writing, but we can concurrently read from the object without being blocked by other readers. Readers will, however, be blocked by writers, which should be infrequent.
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 requires more code, but it allows nearly unlimited concurrent reads 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 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, for example, both get
and set
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 that 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 having to consider thread safety or use it in a thread-unsafe manner.
Summary
These are some of the patterns you can use to defuse any concurrency issues with a mutable state without having to resort to putting synchronization on everything. Happy coding!
Published at DZone with permission of Andy Gibson, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments