Search code examples
javaconcurrencyjava.util.concurrent

wait for all jobs in a ScheduledExecutorService to finish, while allowing new jobs to be added


I am using a ScheduledExecutorService to schedule and process jobs across several threads. In my application, a job can schedule a new job (on the same ScheduledExecutorService), as some kind of follow-up action.

In the main thread, I want to wait until all jobs are finished, as a synchronization point. There are the shutdown() and awaitTermination() methods, but this disallows any running or pending job to schedule a new job. In my case, I actually want to allow this, accepting the risk that we will never finish (or hit some timeout).

How do I wait for all jobs and possibly their follow-up jobs to finish?


Solution

  • It's possible by keeping track of the number of active jobs. Effectively, each job that you submit must be wrapped as follows (pseudo code):

    increase active jobs by one
    try {
        run actual job
    } finally {
        decrease active jobs by one
    }
    

    Of course the ScheduledExecutorService needs to be fully encapsulated for this to work.

    The next step is to find the right concurrent mechanism for this, that doesn't introduce any busy waiting.

    A quick and dirty attempt using a Lock with a Condition that signals no more jobs:

    private final Lock lock = new ReentrantLock();
    private final Condition noJobs = lock.newCondition();
    
    private long jobCount = 0;
    
    private void increaseJobCount() {
        lock.lock();
        try {
            jobCount++;
        } finally {
            lock.unlock();
        }
    }
    
    private void decreaseJobCount() {
        lock.lock();
        try {
            jobCount--;
            if (jobCount == 0) {
                noJobs.signalAll();
            }
        } finally {
            lock.unlock();
        }
    }
    
    public void awaitNoJobs() throws InterruptedException {
        lock.lock();
        try {
            while (jobCount > 0) {
                noJobs.await();
            }
        } finally {
            lock.unlock();
        }
    }
    

    I checked some other known concurrent classes, but apart from a BlockingQueue / BlockingDeque I couldn't find any. Those take up more memory though, as you'd have to add and remove something for each job that's submitted. A CountDownLatch comes close but doesn't allow increasing the count.