14

I'm using java.util.Timer to schedule a periodic task. At one point, I'd like to shut it down, and wait for it to finish.

Timer.cancel() will prevent any future tasks from running. How do I make sure any tasks are not running at the moment (or wait for them if they are?)

I can introduce external synchronization mechanisms, but I don't see how they can cover all cases. For example, if I synchronize on some Monitor within the task, I still miss the case when the task just started executing but didn't take the monitor.

What is the recommended practice for waiting until all tasks are really done, including currently running tasks?

ripper234
  • 222,824
  • 274
  • 634
  • 905

3 Answers3

25

You would be better using an ScheduledExecutorService instead of a Timer to schedule your periodic task. ScheduledExecutorService provides a shutdown() method that will execute any pending tasks. You can then call awaitTermination() to wait for shutdown() to finish.

Mark
  • 28,783
  • 8
  • 63
  • 92
  • 1
    +1. Or, if you have to use a `Timer` for some reason, then you can use a Condition (http://java.sun.com/j2se/1.5.0/docs/api/java/util/concurrent/locks/Condition.html) to synchronize two threads on an event (such as completion of the scheduled task). – Vinay Sajip Aug 24 '09 at 11:04
  • 2
    Yeah, item 68 in Effective Java (2nd ed) recommends ScheduledThreadPoolExecutor as a more flexible replacement for Timer. +1 for mentioning this, as recommended practices were asked for. – Jonik Aug 24 '09 at 11:06
  • Or use a CountdownLatch etc. http://stackoverflow.com/questions/289434/how-to-make-a-java-thread-wait-for-another-threads-output – rogerdpack May 05 '16 at 23:09
  • `ScheduledExecutorService` or its implementation `ScheduledThreadPoolExecutor` are not "replacements" for `Timer`. The methods `ScheduledThreadPoolExecutor#scheduleWithFixedDelay(...)` and `Timer#schedule(...)` (documented as "repeated fixed-delay execution") behave fundamentally different and thus `ScheduledThreadPoolExecutor` has no replacement for this method. This anwer is invalid for people who rely on `Timer#schedule(...)`. I explained the difference more detailed as part of this answer https://stackoverflow.com/a/71761712/3882565. – stonar96 Apr 07 '22 at 23:35
  • @stonar96 I see your point but the question being asked was how to schedule a task, shut it down and wait for it to finish which ScheduleExecutorService does support. – Mark Apr 13 '22 at 13:36
1

This is my solution with ScheduledExecutorService:

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

...
public void startScheduler() {
    ScheduledExecutorService timerService = Executors.newSingleThreadScheduledExecutor();
    MyTimer myTimer = new MyTimer(timerService);

    timerService.scheduleAtFixedRate(myTimer, 1, 1, TimeUnit.SECONDS);
    try {
        boolean isTimerDone = timerService.awaitTermination(22, TimeUnit.SECONDS);
        if(!isTimerDone) {
            timerService.shutdown();
        }
        ...
    } catch (InterruptedException e) {
        ...
    }
}
...

And

import java.util.TimerTask;
import java.util.concurrent.ScheduledExecutorService;

@RequiredArgsConstructor
public class MyTimer extends TimerTask {
    private final ScheduledExecutorService scheduledExecutorService;           

    @Override
    public void run() {
        ...
        if(whenMyBusinessDone) {
            this.cancel();
            scheduledExecutorService.shutdown();
        }
    }
}
0

Something like below might help your needs-

import java.util.Timer;
import java.util.TimerTask;

public class TimerGracefulShutdown {
    public static void main(String[] args) throws InterruptedException {
        //This is a synchronization helper class
        SyncHelper syncHelper = new SyncHelper();

        TimerManager myTimerManager = new TimerManager(syncHelper);

        //Try stopping timer after 5 seconds (it wont stop until the 30 seconds sleep of timertask does not finish)
        Thread.currentThread().sleep(5000);
        System.out.println("Going to stop my timer now");
        myTimerManager.stopTimer();
        System.out.println("Cancelled timer");
    }
}

class TimerManager {

    SyncHelper syncHelper;
    Timer timer;

    public TimerManager(SyncHelper syncHelper) {
        this.syncHelper = syncHelper;
        startTimer();
    }

    private void startTimer() {
        timer = new Timer(true);
        TimerTask myTask = new MyTimerTask(syncHelper);
        timer.scheduleAtFixedRate(myTask, 0, 100000);
    }

    public void stopTimer() {
        try {
            syncHelper.testAndSetOrReset("acquire");
        } catch(Exception e) {
            e.printStackTrace();
        }

        //Shutdown the timer here since you know that your timertask is not executing right now.
        timer.cancel();
        try {
            syncHelper.testAndSetOrReset("release");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

class MyTimerTask extends TimerTask {

    private SyncHelper syncHelper;

    public MyTimerTask(SyncHelper syncHelper) {
        this.syncHelper = syncHelper;
    }

    public void run() {
        try {
            syncHelper.testAndSetOrReset("acquire");
        } catch (Exception e1) {
            e1.printStackTrace();
        }

        System.out.println("Over here");
        try {
            Thread.currentThread().sleep(30000);
        } catch(Exception e) {

        }
        System.out.println("Done sleeping");

        //Finally release the helper.
        try {
            syncHelper.testAndSetOrReset("release");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

class SyncHelper {

    private int index = 0;

    public synchronized void testAndSetOrReset(String command) throws Exception {

        if("acquire".equals(command)) { 
            if(index == 1) {
                wait();
            }
            index++;
        } else if("release".equals(command)) {
            index--;
            notifyAll();
        }
    }
}
Keshav
  • 4,408
  • 8
  • 31
  • 50
  • You should use `Thread.sleep(...)` instead of `Thread.currentThread().sleep(...)` because it's a static method; your code might tempt you to do `someOtherThread.sleep(...)` which does not sleep `someOtherThread`. – newacct Sep 19 '09 at 04:38