如何在Java中设置计时器?

174
如何设置一个计时器,比如2分钟,在尝试连接数据库后,如果连接出现任何问题,则抛出异常?

1
请问楼主能否澄清一下,是要尝试至少2分钟的操作,还是无论何时都必须立即抛出异常,即使当前正在尝试连接? - thecoshman
5个回答

314

因此,答案的第一部分是如何执行主题要求的操作,因为这是我最初的解释,而且好像有些人觉得有帮助。问题已经澄清,我已经扩展了答案来解决这个问题。

设置定时器

首先需要创建一个计时器(这里使用java.util版本):

import java.util.Timer;

..

Timer timer = new Timer();

要运行一次任务,您需要执行以下操作:

timer.schedule(new TimerTask() {
  @Override
  public void run() {
    // Your database code here
  }
}, 2*60*1000);
// Since Java-8
timer.schedule(() -> /* your database code here */, 2*60*1000);

要让任务在一定时间后重复执行,您需要执行以下操作:

timer.scheduleAtFixedRate(new TimerTask() {
  @Override
  public void run() {
    // Your database code here
  }
}, 2*60*1000, 2*60*1000);

// Since Java-8
timer.scheduleAtFixedRate(() -> /* your database code here */, 2*60*1000, 2*60*1000);

设置任务超时时间

为了特定地完成澄清后的问题,即尝试在给定的时间内执行任务,您可以执行以下操作:

ExecutorService service = Executors.newSingleThreadExecutor();

try {
    Runnable r = new Runnable() {
        @Override
        public void run() {
            // Database task
        }
    };

    Future<?> f = service.submit(r);

    f.get(2, TimeUnit.MINUTES);     // attempt the task for two minutes
}
catch (final InterruptedException e) {
    // The thread was interrupted during sleep, wait or join
}
catch (final TimeoutException e) {
    // Took too long!
}
catch (final ExecutionException e) {
    // An exception from within the Runnable task
}
finally {
    service.shutdown();
}

如果任务在2分钟内完成,则会正常执行,否则将抛出TimeoutException异常。

一个问题是,尽管两分钟后您会收到TimeoutException异常,但任务实际上仍将继续运行,尽管可能数据库或网络连接最终将超时并在线程中抛出异常。但请注意,它可能会一直占用资源,直到发生异常。


8
读者注意:这个答案是明显错误的。它设置了一个定时器等待2分钟,然后在这2分钟延迟之后运行一个数据库查询,然后每2分钟再次运行该查询。它没有立即运行数据库查询,然后在2分钟后失败,这正是我认为问题所要求的(在评论中澄清)。 - AgilePro
2
@AgilePro 那是真的。我没有按照最终陈述的问题回答,现在我已经更新了它来解决这个问题。 - andrewmu
1
@ErnestasGruodis 核心 API 将构造函数列为 public:http://docs.oracle.com/javase/7/docs/api/java/util/Timer.html#Timer()。您的 classpath 可能有不同的 Timer 类,尝试使用 java.util.Timer 作为类。 - andrewmu
4
你如何在使用lambda表达式时(自Java 8以来)使用timer.schedule()方法? TimerTask(第一个参数)甚至不是功能性接口。 TimerTask只是一个抽象类。 使用lambda表达式可以简化TimerTask的创建过程,并使代码更加易读。要使用timer.schedule()方法与lambda表达式,您需要提供一个实现Runnable接口的lambda表达式作为第一个参数,而不是传递一个TimerTask对象。例如:Timer timer = new Timer(); timer.schedule(() -> { // 在这里编写定时任务的代码 }, delay, period);在上面的示例中,lambda表达式替换了TimerTask对象,并且在定时任务需要执行的代码处提供了一个代码块。您还需要提供延迟和重复间隔参数,以指定定时任务的执行计划。 - User8500049
3
TimerTask不是一个函数式接口!https://dev59.com/LloT5IYBdhLWcg3w-DO6 - Tharindu Sathischandra
显示剩余6条评论

28

使用这个

long startTime = System.currentTimeMillis();
long elapsedTime = 0L.

while (elapsedTime < 2*60*1000) {
    //perform db poll/check
    elapsedTime = (new Date()).getTime() - startTime;
}

//Throw your exception

1
嗯...从技术上讲,这可能有效,但它并没有涵盖边缘情况,例如当你正在执行数据库轮询时时间耗尽,而这可能是OP所需的。 - thecoshman
1
这是唯一正确的答案。它将在单个线程上运行,并且将计算出没有漂移的结束时间。在这种情况下使用TimerTask完全是错误的选择。我对StackOverflow上有多少类似错误的示例感到惊讶。 - AgilePro
1
@thecoshman - 计时器实现一旦进程开始,不会中断数据库操作,也不会中断其他操作。无论如何,您只能控制启动数据库操作的时间。有一个普遍的误解,认为您需要“计时器”才能计时,但实际上不需要。您只需要执行某些操作,并使用当前时间确保您不要执行太长时间即可。 - AgilePro
20
这实际上是非常糟糕的编码风格,因为while循环会不断运行并检查事物...这对CPU和电池寿命都有害。 - Infinite
@nimWM 这个答案使用了Date类。不幸的是,Joshua Bloch的《Effective Java》第50条建议:“Date已经过时,新代码中不应再使用它。” - Brian Schack
显示剩余6条评论

12

好的,我现在明白你的问题了。你可以使用Future去尝试做某事,并在一段时间后如果没有发生任何事情就超时。

例如:

FutureTask<Void> task = new FutureTask<Void>(new Callable<Void>() {
  @Override
  public Void call() throws Exception {
    // Do DB stuff
    return null;
  }
});

Executor executor = Executors.newSingleThreadScheduledExecutor();
executor.execute(task);

try {
  task.get(5, TimeUnit.SECONDS);
}
catch(Exception ex) {
  // Handle your exception
}

抛出了异常,但程序没有终止。请帮我解决这个问题。 - Ankita
2
你可以使用ExecutorService类来代替Executor。它有shutdown()方法来停止执行器。 - Rites
1
执行器将会吞噬你在Callable/Runnable中没有特别处理的抛出异常。如果你的Runnable/Callable没有捕获和处理异常并且它是由其他人提供给你而不是你自己拥有的,那么你需要继承ScheduledExecutorService并覆盖afterExecute方法(确保调用super.afterExecute())。afterExecute的第二个参数将是来自Runnable/Callable的throwable。 - adam

4
[Android] 如果有人想使用Java在Android上实现计时器,可以按照以下方式使用UI线程执行操作。
Timer timer = new Timer();
timer.schedule(new TimerTask() {
           @Override
            public void run() {
                ActivityName.this.runOnUiThread(new Runnable(){
                    @Override
                      public void run() {
                       // do something
                      }        
                });
            }
        }, 2000));

4
    new java.util.Timer().schedule(new TimerTask(){
        @Override
        public void run() {
            System.out.println("Executed...");
           //your code here 
           //1000*5=5000 mlsec. i.e. 5 seconds. u can change accordngly 
        }
    },1000*5,1000*5); 

只需复制粘贴以上代码,它将正常运行。我给了两个'time',第一个是第一次执行此代码的时间,第二个是间隔时间。 - Mahadev Mane
2
Timer的文档建议使用Executor框架。https://docs.oracle.com/javase/10/docs/api/java/util/Timer.html - Karan Khanna

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