A typical example is the publication of read-only resources that will be shared by all tasks in a thread pool: one of my projects at work parses and processes a few thousands XML files in parallel. I use XStream for the parsing. The javadoc of XStream — the base class that lets you configure the framework and [de]serialize objects — states that it is safe to use by multiple threads once it has been configured. So it makes sense to use a single instance all along:
public class GoodPiggy {
private ExecutorService executor;
private XStream xstream;
public void init() {
executor = Executors.newFixedThreadPool(someSize);
final XStream xstream = new XStream();
// configure xstream...
}
private class Task implements Runnable {
public void run() {
// do something with xstream... (threadsafe)
}
}
public void submit() {
executor.submit(new Task());
}
}The important question here is: are the changes to xstream safely published, i.e. do the worker threads running the tasks "see" the configuration performed in init()? This is where piggybacking comes into play: submitting a task to an executor happens-before the task begins execution, so the code using xstream in run() is guaranteed to see it in a consistent state (assuming of course that init() and submit() are run by the same thread).JCIP warns that piggybacking on synchronization is a fragile construct, but I think this case is a reasonable use.
Note:
Of course, things don't work so well if the tasks need to modify the shared state. Here is a counter-example (adapted from item 66 of Effective Java, 2nd edition):
public class BadPiggy { // DOESN'T WORK
private static boolean stop;
public static void main(final String[] args) throws InterruptedException {
final ExecutorService executor = Executors.newFixedThreadPool(2);
Runnable looping = new Runnable() {
public void run() {
int i = 0;
while (!stop) i++;
}
};
executor.submit(looping);
Runnable stopper = new Runnable() {
public void run() { stop = true; }
};
executor.submit(stopper);
executor.shutdown();
}
}Here the modification of stop is not safely published, because there is no happens-before relationship between the moment stopper sets it, and the moment looping is expected to see the change. I can get this example to loop forever on my machine (windows, Java 6u16 VM with -server option).The solution here is to make the field volatile, which guarantees visibility of the changes. If we needed mutual exclusion as well (for instance, if the shared state was an incrementing counter), we would need synchronization, either explicitly, or through a higher-level construct (like an AtomicInteger).

0 comments:
Post a Comment