可重置的 Java 计时器。

40

我想在Java中使用一个可重置时间的java.utils.Timer。我需要设置一个一次性事件在X秒内发生。如果在计时器创建和X秒之间什么都没有发生,那么事件将按正常方式发生。

但是,如果在X秒之前,我决定事件应该在Y秒后发生,那么我希望能够告诉计时器重置时间,以便事件在Y秒后发生。 例如,计时器应该能够执行以下操作:

Timer timer = new Timer();  
timer.schedule(timerTask, 5000); //Timer starts in 5000 ms (X)

//At some point between 0 and 5000 ms...  
setNewTime(timer, 8000);  //timerTask will fire in 8000ms from NOW (Y).

使用utils计时器无法实现此操作,因为如果调用cancel()函数,则无法再次进行调度。

我找到的与其行为最接近的方法是使用javax.swing.Timer,需要停止原始计时器并创建一个新的计时器。例如:

timer.stop();
timer = new Timer(8000, ActionListener);
timer.start();

有更简单的方式吗??

8个回答

50
根据Java 1.5及以后的Timer文档,您应该优先使用ScheduledThreadPoolExecutor。 (您可以使用Executors.newSingleThreadScheduledExecutor()来创建此执行程序以便于使用; 它创建了类似于Timer的东西。)
有趣的是,当您安排任务(通过调用schedule())时,它会返回一个ScheduledFuture对象。 您可以使用它来取消已安排的任务。 然后,您可以自由地提交具有不同触发时间的新任务。
ETA:链接到的Timer文档没有提到ScheduledThreadPoolExecutor,但OpenJDK版本中有这样的说明:
Java 5.0引入了java.util.concurrent包和其中的一种并发实用工具,即ScheduledThreadPoolExecutor,它是一个线程池,用于重复在给定的速率或延迟下执行任务。 它实际上是Timer/TimerTask组合的更多功能替代品,因为它允许多个服务线程,接受各种时间单位,并且不需要对TimerTask进行子类化(只需实现Runnable)。 将ScheduledThreadPoolExecutor配置为一个线程使其等效于Timer

ScheduledThreadPoolExecutor在这个Timer中被提到:http://developer.android.com/reference/java/util/Timer.html - Erik B
这种方法的问题在于,您不再可以在任务内部访问TimerTask.scheduledExecutionTime()。因此,您将无法确定该任务是否“迟到”并且不应执行。 - Jonathan Spooner
2
使用myScheduledThreadPoolExecutor.getQueue().clear()来清除所有尚未运行的预定任务,但是对于Executors.newSingleThreadScheduledExecutor(),您无法使用此技术。相反,您必须实例化一个new ScheduledThreadPoolExecutor(1),因为只有在执行器返回的对象上暴露接口方法。 - gMale
不要使用 @gMale 的建议,而是使用 executor.setRemoveOnCancelPolicy(true); - Mark Jeronimus

17

如果您的Timer只需要执行一个任务,那么我建议您进行子类化:

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

public class ReschedulableTimer extends Timer
{
    private Runnable  task;
    private TimerTask timerTask;

    public void schedule(Runnable runnable, long delay)
    {
        task = runnable;
        timerTask = new TimerTask()
        {
            @Override
            public void run()
            {
                task.run();
            }
        };
        this.schedule(timerTask, delay);
    }

    public void reschedule(long delay)
    {
        timerTask.cancel();
        timerTask = new TimerTask()
        {
            @Override
            public void run()
            {
                task.run();
            }
        };
        this.schedule(timerTask, delay);
    }
}
你需要修改代码以添加误用检查,但它应该能够实现你想要的功能。ScheduledThreadPoolExecutor似乎也没有内置支持重新调度现有任务的功能,但类似的方法也可以在那里使用。

3
整段代码如下...希望对您有所帮助。
{

        Runnable r = new ScheduleTask();
        ReschedulableTimer rescheduleTimer = new ReschedulableTimer();
        rescheduleTimer.schedule(r, 10*1000);


    public class ScheduleTask implements Runnable {
        public void run() {
            //Do schecule task

        }
      }


class ReschedulableTimer extends Timer {
        private Runnable task;
        private TimerTask timerTask;

        public void schedule(Runnable runnable, long delay) {
          task = runnable;
          timerTask = new TimerTask() { 
              public void run() { 
                  task.run(); 
                  }
              };

          timer.schedule(timerTask, delay);        
        }

        public void reschedule(long delay) {
            System.out.println("rescheduling after seconds "+delay);
          timerTask.cancel();
          timerTask = new TimerTask() { 
              public void run() { 
                  task.run(); 
              }
          };
          timer.schedule(timerTask, delay);        
        }
      }


}

1

这是我正在尝试的内容。我有一个类,使用TimerTask每60秒轮询数据库。

在我的主类中,我保留了Timer的实例和我的本地TimerTask子类的实例。主类有一个方法来设置轮询间隔(从60到30)。在此方法中,我取消了我的TimerTask(它是我的子类,在其中重写了cancel()方法以进行一些清理,但这不应该有影响),然后将其设置为null。我重新创建了一个新实例,并在现有的Timer中按照新的间隔安排了新实例。

由于Timer本身没有被取消,它使用的线程保持活动状态(任何其他TimerTasks内部也是如此),旧的TimerTask被替换为一个新的TimerTask,它恰好是相同的,但是处于VIRGIN状态(因为旧的TimerTask已经被执行或安排,所以不再是VIRGIN,这是调度所必需的)。

当我想要关闭整个计时器时,我会取消并将TimerTask设置为null(与更改时间时所做的操作相同,再次清理我的TimerTask子类中的资源),然后取消并将Timer本身设置为null。


1

你需要安排一个重复任务吗?如果是的话,我建议你考虑使用Quartz


1

我认为使用 Timer/TimerTask 是不可能实现的,但是根据你想要实现的具体目标,你可能会满意于使用 java.util.concurrent.ScheduledThreadPoolExecutor


0

这是可重置计时器的示例。尝试根据您的需要进行更改...

package com.tps.ProjectTasks.TimeThread;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

/**
 * Simple demo that uses java.util.Timer to schedule a task to execute
 * every 5 seconds and have a delay if you give any input in console.
 */

public class DateThreadSheduler extends Thread {  
    Timer timer;
    BufferedReader br ;
    String data = null;
    Date dNow ;
    SimpleDateFormat ft;

    public DateThreadSheduler() {

        timer = new Timer();
        timer.schedule(new RemindTask(), 0, 5*1000); 
        br = new BufferedReader(new InputStreamReader(System.in));
        start();
    }

    public void run(){

        while(true){
            try {
                data =br.readLine();
                if(data != null && !data.trim().equals("") ){
                    timer.cancel();
                    timer = new Timer();
                    dNow = new Date( );
                    ft = new SimpleDateFormat ("E yyyy.MM.dd 'at' hh:mm:ss a zzz");
                    System.out.println("Modified Current Date ------> " + ft.format(dNow));
                    timer.schedule(new RemindTask(), 5*1000 , 5*1000);
                }

            }catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String args[]) {
        System.out.format("Printint the time and date was started...\n");
        new DateThreadSheduler();
    }
}

class RemindTask extends TimerTask {
    Date dNow ;
    SimpleDateFormat ft;

    public void run() {

        dNow = new Date();
        ft = new SimpleDateFormat ("E yyyy.MM.dd 'at' hh:mm:ss a zzz");
        System.out.println("Current Date: " + ft.format(dNow));
    }
}

这个例子每隔5秒钟打印当前日期和时间...但如果您在控制台中输入任何内容,计时器将延迟执行给定的输入任务...


0
我为类似的目的编写了一个自己的计时器类,你可以随意使用:
public class ReschedulableTimer extends Timer {
  private Runnable mTask;
  private TimerTask mTimerTask;

  public ReschedulableTimer(Runnable runnable) {
    this.mTask = runnable;
  }

  public void schedule(long delay) {
    if (mTimerTask != null)
      mTimerTask.cancel();

    mTimerTask = new TimerTask() {
      @Override
      public void run() {
        mTask.run();
      }
    };
    this.schedule(mTimerTask, delay);
  }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接