I use an ExecutorService
to execute a task. This task can recursively create other tasks which are submitted to the same ExecutorService
and those child tasks can do that, too.
I now have the problem that I want to wait until all the tasks are done (that is, all tasks are finished and they did not submit new ones) before I continue.
I cannot call ExecutorService.shutdown()
in the main thread because this prevents new tasks from being accepted by the ExecutorService
.
And Calling ExecutorService.awaitTermination()
seems to do nothing if shutdown
hasn't been called.
So I am kinda stuck here. It can't be that hard for the ExecutorService
to see that all workers are idle, can it? The only inelegant solution I could come up with is to directly use a ThreadPoolExecutor
and query its getPoolSize()
every once in a while. Is there really no better way do do that?
If number of tasks in the tree of recursive tasks is initially unknown, perhaps the easiest way would be to implement your own synchronization primitive, some kind of "inverse semaphore", and share it among your tasks. Before submitting each task you increment a value, when task is completed, it decrements that value, and you wait until the value is 0.
Implementing it as a separate primitive explicitly called from tasks decouples this logic from the thread pool implementation and allows you to submit several independent trees of recursive tasks into the same pool.
Something like this:
public class InverseSemaphore {
private int value = 0;
private Object lock = new Object();
public void beforeSubmit() {
synchronized(lock) {
value++;
}
}
public void taskCompleted() {
synchronized(lock) {
value--;
if (value == 0) lock.notifyAll();
}
}
public void awaitCompletion() throws InterruptedException {
synchronized(lock) {
while (value > 0) lock.wait();
}
}
}
Note that taskCompleted()
should be called inside a finally
block, to make it immune to possible exceptions.
Also note that beforeSubmit()
should be called by the submitting thread before the task is submitted, not by the task itself, to avoid possible "false completion" when old tasks are completed and new ones not started yet.
EDIT: Important problem with usage pattern fixed.