Anyone who has tried knows what messy task deboning a chicken is (unless you are Morou Ouattara.) Likewise I face the messy task of protecting multi-threaded legacy code from itself.
I face a complex graph with an elaborate API. It is thread-safe in that individual nodes are locked, but it is not thread-consistent: changes traversing the graph are not isolated from each other.
What to do?
One approach which is of general application is to dethread the data structure. That is, turn it from multi-threaded access to single-threaded.
The task is made much simpler by the JDK5 concurrency library. First, create a facade for the original API:
interface Complexity {
void methodOne(int argOne);
int methodTwo();
// ... ad nauseum
}
Next create command classes for each method call in the API:
class MethodOne
extends Runnable {
private final Complexity realGraph;
private final int argOne;
MethodOne(Complexity realGraph, int argOne) {
this.realGraph = realGraph;
this.argOne = argOne;
}
void run() {
realGraph.methodOne(argOne);
}
}
class MethodTwo
extends Callable<Integer> {
private final Complexity realGraphy;
MethodTwo(final Complexity realGraph) {
this.realGraph = realGraph;
}
Integer call() {
return realGraph.methodTwo();
}
}
// ... ad nauseum
Finally, wire the facade to forward calls to a queue for replay into the real graph with a dedicated single thread:
class SingleThreadComplexity implements Complexity {
private final ExecutorService commands
= Executors.newSingleThreadExecutor();
private final Complexity realGraph
= new OriginalMultiThreadComplexity();
public void methodOne(int argOne) {
commands.submit(new MethodOne(realGraph, argOne);
}
public int methodTwo() {
try {
return commands.submit(new MethodTwo(realGraph)).get();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}
// ... ad nauseum
}
Finishing up
Unless the caller has special latency needs violated by the queueing of commands, the facade is a drop-in replacement for the original complex graph but with the new property that the original is always accessed sequentially from the same thread. Goetz calls this property thread confinement.
The complex, messy graph—a fowl thing indeed—is now dethreaded.