2

I'm still battling to understand how to work with the ScheduledExecutorService. I want to have a single ScheduledExecutorService/ScheduledThreadPoolExecutor, which schedules multiple tasks and lets them run a fixed amount of times. As soon as no more tasks are scheduled, I want to shutdown the ScheduledThreadPoolExecutor. I'm trying to understand how this could work by writing some tests. The first thing I tried is the following:

    @Test
    public void testExecutorService() throws Exception {
        ScheduledThreadPoolExecutor executorService = new ScheduledThreadPoolExecutor(1);
        executorService.setRemoveOnCancelPolicy(true);
        CountDownLatch latchOne = new CountDownLatch(5);
        CountDownLatch latchTwo = new CountDownLatch(10);

        ScheduledFuture<?> scheduledFutureOne = executorService.scheduleAtFixedRate(new MyRunnerOne(latchOne), 0, 1, TimeUnit.SECONDS);
        ScheduledFuture<?> scheduledFutureTwo = executorService.scheduleAtFixedRate(new MyRunnerTwo(latchTwo), 0, 2, TimeUnit.SECONDS);
        
        latchOne.await();
        scheduledFutureOne.cancel(false);
        latchTwo.await();
        scheduledFutureTwo.cancel(false);
        
        System.out.println("Shutting down service...");
        executorService.shutdown();
        Thread.sleep(100);
        assertTrue(executorService.isTerminated());
    }

    class MyRunnerOne implements Runnable
    {
        CountDownLatch latch;

        MyRunnerOne(CountDownLatch latch)
        {
            this.latch = latch;
        }

        @Override
        public void run()
        {
            System.out.println("Do Task One : " + latch.getCount());
            latch.countDown();
        }
    }


    class MyRunnerTwo implements Runnable
    {
        CountDownLatch latch;

        MyRunnerTwo(CountDownLatch latch)
        {
            this.latch = latch;
        }

        @Override
        public void run()
        {
            System.out.println("Do Task Two: " + latch.getCount());
            latch.countDown();
        }
    }

This works well and I get the expected output:

Do Task One : 5
Do Task Two: 10
Do Task One : 4
Do Task One : 3
Do Task Two: 9
Do Task One : 2
Do Task One : 1
Do Task Two: 8
Do Task Two: 7
Do Task Two: 6
Do Task Two: 5
Do Task Two: 4
Do Task Two: 3
Do Task Two: 2
Do Task Two: 1
Shutting down service...

However, I this only works because I am waiting for latchOne before waiting for latchTwo, since the first task takes less time. If I switch this order, i.e.

        latchTwo.await();
        scheduledFutureTwo.cancel(false);
        latchOne.await();
        scheduledFutureOne.cancel(false);

Task One will be executed further, even when the latch has reached zero, because it is only cancelled after Task Two. Is there a way to execute something (scheduledFutureOne.cancel(false) in this case) as soon as the latch is zero and doing so for multiple latches at once?

I also tried to follow the setup described in this answer which again works well but I do not know where/how I would shutdown the ExecutorService. The setup is similar, an atomic integer is increased until the number of desired invocations is reached and then we cancel the future. This still does not shutdown the ExecutorService though.

TLDR I would like to schedule multiple tasks with a single ExecutorService, each for a fixed number of invokations. The order in which the tasks are handled by the ExecutorService should be allowed to be random. As soon as no tasks are scheduled anymore, the ExecutorService should be shutdown.

Thank you guys for any ideas :)

Annylowell
  • 57
  • 4
  • Is it an option to schedule one big task which does the five or ten task in a simple `for` loop inside the `run()` method? Is it an option to immediately schedule the same task five or ten times? – Progman Dec 28 '21 at 13:52
  • Any particular reason why you want to shut it down? Any particular reason why `Executors.newCachedThreadPool()` doesn't satisfy your requirement? – Shark Dec 28 '21 at 17:26
  • Hello, thanks for both your answers. @Progman I don't really like the first option, because this was just an example, it would be more like 100000 tasks and then a for loop seems somewhat clumsy. I don't see a problem in scheduling the tasks all at once, but also don't really see the benefit yet? – Annylowell Dec 29 '21 at 07:18
  • @Shark Honestly I just want to shut it down because it seems like that would be the right thing to do - if I don't use a resource anymore, clean it up. And it will be invoked multiple times, so I don't want to have many idle executorServices lying around at some point. I just read up on newCachedThreadPool and it seems quite nice, even though some people still say one should call shutdown here as well. I will read some more, thanks for the suggestions :) – Annylowell Dec 29 '21 at 07:19
  • 1
    hmm.... i dunno. having one ExecutorService thats initialized on app start and shut down when app should stop somehow seems better (to me) than initializing ExecutorService, running a batch of tasks, and shutting it down - **every time** you want to perform some tasks. – Shark Dec 29 '21 at 09:55

1 Answers1

0

It will be easier to do if you make the tasks reschedule themselves in the executor service:

ScheduledThreadPoolExecutor executorService = new ScheduledThreadPoolExecutor(1);
CountDownLatch latchOne = new CountDownLatch(1);
CountDownLatch latchTwo = new CountDownLatch(1);

executorService.submit(new MyRunnerOne(executorService, latchOne, 5));
executorService.submit(new MyRunnerTwo(executorService, latchTwo, 10));

latchTwo.await();
latchOne.await();

System.out.println("Shutting down service...");
executorService.shutdown();
executorService.awaitTermination(100, TimeUnit.SECONDS);
System.out.println("Terminated: " + executorService.isTerminated());

//...

class MyRunnerOne implements Runnable {
    private final ExecutorService service;
    private final CountDownLatch latch;
    private final int count;

    MyRunnerOne(ExecutorService service, CountDownLatch latch, int count) {
        this.service = service;
        this.latch = latch;
        this.count = count;
    }

    @Override
    public void run() {
        System.out.println("Do Task One : " + count);
    
        if (count > 1) {
            service.submit(new MyRunnerOne(service, latch, count - 1));
        } else {
            latch.countDown();
        }
    }
}

Full code.

Alexey Veleshko
  • 792
  • 1
  • 18
  • 1
    Thanks for your suggestion, I will have a closer look at this option. It seems like some of the nice features of schedule will get lost, but I can probably work around that :) – Annylowell Dec 29 '21 at 07:21