Invoking a java Timer-Task from the timer thread

2019-08-22 04:59发布

问题:

This is about java's built-in Timer-class. I need a method to be invoked frequently at certain set intervals, but if the execution takes too long, I don't want them to pile up, so the solution would be to queue up the next execution at the beginning of said method. Of course this means that the queueing happens in the same queue Thread. Would this work out or would this cause problems?

public class SomeClass extends TimerTask {
   public static SomeClass timer;
   public void run() {
      timer.schedule(this,100);
      //do stuff
   }

}

回答1:

Java has the executor services to do exactly what you want to do. Have a look at the method ScheduledExecutorService#scheduleWithFixedDelay(). As opposed to the method ScheduledExecutorService#scheduleAtFixedRate(), the method with fixed delay does not try to keep up.

Here is an example:

public void run() {
    Executors.newSingleThreadScheduledExecutor().scheduleWithFixedDelay(
            this::task, 0, 10, TimeUnit.MILLISECONDS);
}

public void task() {
    // run your task
}

Indeed, your use case seems to be not covered by the standard library methods. However, you should be able to use the following class to do what you want.

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

public class TimerExample {
    private final ScheduledExecutorService executor = Executors
            .newSingleThreadScheduledExecutor();
    private final Runnable task;
    private final Consumer<Exception> onException;
    private final long delay;
    private final TimeUnit unit;

    public TimerExample(Runnable task, Consumer<Exception> onException,
            long delay, TimeUnit unit) {
        this.task = task;
        this.onException = onException;
        this.delay = delay;
        this.unit = unit;
    }

    public void start() {
        executor.execute(this::execute);
    }

    private void execute() {
        executor.schedule(this::execute, delay, unit);
        try {
            task.run();
        } catch (Exception e) {
            onException.accept(e);
        }
    }
}

And here the usage:

    new TimerExample(() -> System.out.println("."),
            Exception::printStackTrace, 20, TimeUnit.MILLISECONDS).start();

Since it uses a single threaded executor service, it will not start the next execution until the previous is finished. Also, it schedules the next execution before the task itself is executed.

Also, see this SO-question about why you should prefer the executor services above the Timer class.

You still have to implement the shutdown mechanism by yourself.