I am writing a program that does some batch processing. The batch elements can be processed independently of each other and we want to minimize overall processing time. So, instead of looping through each element in the batch one at a time, I am using an ExecutorService and submitting Callable objects to it:
public void process(Batch batch)
{
ExecutorService execService = Executors.newCachedThreadPool();
CopyOnWriteArrayList<Future<BatchElementStatus>> futures = new CopyOnWriteArrayList<Future<BatchElementStatus>>();
for (BatchElement element : batch.getElement())
{
Future<MtaMigrationStatus> future = execService.submit(new ElementProcessor(batch.getID(),
element));
futures.add(future);
}
boolean done = false;
while (!done)
{
for (Future<BatchElementStatus> future : futures)
{
try
{
if (future.isDone())
{
futures.remove(future);
}
}
catch (Exception e)
{
System.out.println(e.getMessage());
}
if (futures.size() == 0)
{
done = true;
}
}
}
}
We want to be able to allow the batch processing to be cancelled. Because I’m not using a loop, I can’t just check at the top each loop if a cancel flag has been set.
We are using a JMS topic to which both the BatchProcessor and ElementProcessor will be listening to inform them the batch has been cancelled.
There are a number of steps in the ElementProcess call() after which some of them the processing can be safely stopped but there’s a point of no return. The class has this basic design:
public class ElementProcessor implements Callable, MessageListener
{
private cancelled = false;
public void onMessage(Message msg)
{
// get message object
cancelled = true;
}
public BatchElementStatus call()
{
String status = SUCCESS;
if (!cancelled)
{
doSomehingOne();
}
else
{
doRollback();
status = CANCELLED;
}
if (!cancelled)
{
doSomehingTwo();
}
else
{
doRollback();
status = CANCELLED;
}
if (!cancelled)
{
doSomehingThree();
}
else
{
doRollback();
status = CANCELLED;
}
if (!cancelled)
{
doSomehingFour();
}
else
{
doRollback();
status = CANCELLED;
}
// After this point, we cannot cancel or pause the processing
doSomehingFive();
doSomehingSix();
return new BatchElementStatus("SUCCESS");
}
}
I’m wondering if there’s a better way to check if the batch/element has been cancelled other than wrapping method calls/blocks of code in the call method in the if(!cancelled) statements.
Any suggestions?
I don’t think you can do much better than what you are currently doing, but here is an alternative:
Also, you want to make
cancelledvolatile, sinceonMessagewill be called from another thread. But you probably don’t want to useonMessageandcancelledanyway (see below).Other minor points: 1)
CopyOnWriteArrayList<Future<BatchElementStatus>> futuresshould just be anArrayList. Using a concurrent collection mislead us into thinking thatfuturesis on many thread. 2)while (!done)should be replaced bywhile (!futures.isEmpty())anddoneremoved. 3) You probably should just callfuture.cancel(true)instead of “messaging” cancellation. You would then have to checkif (Thread.interrupted())instead ofif (cancelled). If you want to kill all futures then just callexecService.shutdownNow(); your tasks have to handle interrupts for this to work.EDIT:
instead of your
while(!done) { for (... futures) { ... }}, you should use an ExecutorCompletionService. It does what you are trying to do and it probably does it a lot better. There is a complete example in the API.