在Java中,Timer和TimerTask与Thread + sleep之间的区别是什么?

109

我发现这里有类似的问题,但是它们都没有满足我的需求。因此,我再次重新提出问题-

我有一个需要定期执行的任务(比如每1分钟执行一次)。相比于创建一个带有无限循环和sleep的新线程,使用Timertask & Timer来完成这个任务有什么优势呢?

使用timertask的代码片段-

TimerTask uploadCheckerTimerTask = new TimerTask(){

 public void run() {
  NewUploadServer.getInstance().checkAndUploadFiles();
 }
};

Timer uploadCheckerTimer = new Timer(true);
uploadCheckerTimer.scheduleAtFixedRate(uploadCheckerTimerTask, 0, 60 * 1000);

使用Thread和sleep的代码片段-

Thread t = new Thread(){
 public void run() {
  while(true) {
   NewUploadServer.getInstance().checkAndUploadFiles();
   Thread.sleep(60 * 1000);
  }
 }
};
t.start();

如果逻辑执行时间超过间隔时间,我就不必担心是否错过某些周期。

请发表您的评论...

更新:
最近我发现使用Timer和Thread.sleep()之间的另一个区别。假设当前系统时间是上午11:00,如果我们由于某种原因将系统时间回滚到上午10:00,那么Timer将停止执行任务,直到它达到上午11:00,而Thread.sleep()方法将继续执行任务而不受干扰。这可能是决定在这两者之间使用什么的重要因素。


23
提议:Timer和TimerTask已经过时,已被ExecutorService有效地取代,尽管您的观点仍然成立。 - skaffman
谢谢你的建议,我决定使用ExecutorService :) - Keshav
非常感谢大家的回答,确实帮助我更好地理解了! - Keshav
7
当只需要一个线程时,计时器并不过时,而且更受欢迎。 - Justin
2
在JME环境中,Timer和TimerTask仍然非常有用,因为ExecutorService不存在(因为JME基于Java 1.3...)。 - スーパーファミコン
7个回答

68

TimerTask的优点在于它能更好地表达你的意图(即代码可读性),并且已经实现了cancel()特性。

请注意,它也可以像您自己的示例一样以更简短的形式编写:

Timer uploadCheckerTimer = new Timer(true);
uploadCheckerTimer.scheduleAtFixedRate(
    new TimerTask() {
      public void run() { NewUploadServer.getInstance().checkAndUploadFiles(); }
    }, 0, 60 * 1000);

如果计时器用于每天,在特定的时间9pm和9am上,如何给出值?在上面的代码中... @Zed? - gumuruh

13

Timer/TimerTask 还考虑了您的任务执行时间,因此它将更加准确。 它处理多线程问题(例如避免死锁等)更好。当然,通常最好使用经过充分测试的标准代码而不是一些自制解决方案。


9
Timer 文档 中可以看到:

Java 5.0 引入了 java.util.concurrent 包,其中的一个并发实用工具是 ScheduledThreadPoolExecutor,它是用于以给定速率或延迟重复执行任务的线程池。它实际上是 Timer/TimerTask 组合的更多功能替代品,因为它允许多个服务线程、接受各种时间单位,并且不需要子类化 TimerTask(只需实现 Runnable)。将 ScheduledThreadPoolExecutor 配置为一个线程相当于 Timer。

因此,建议使用 ScheduledThreadExecutor 而不是 Timer:
  • Timer 使用单个后台线程来顺序执行所有计时器的任务。所以,如果任务不快速完成,则会延迟后续任务的执行。但在 ScheduledThreadPoolExecutor 的情况下,我们可以配置任意数量的线程,也可以通过提供 ThreadFactory 来完全控制。
  • Timer 可能对系统时钟敏感,因为它使用 Object.wait(long) 方法。但是,ScheduledThreadPoolExecutor 不会受到影响。
  • 在 TimerTask 中抛出的运行时异常将终止该特定线程,从而使 Timer 失效,而我们可以在 ScheduledThreadPoolExecutor 中处理它,以便其他任务不会受到影响。
  • Timer 提供了 cancel 方法来终止计时器并丢弃所有已安排的任务,但它不会干扰当前正在执行的任务并让其完成。但如果计时器作为守护线程运行,则无论是否取消,它都将在所有用户线程执行完毕后立即终止。

Timer vs Thread.sleep

Timer 使用 Object.wait 而不是 Thread.sleep

  1. 等待(wait)线程可以被另一个线程通知(使用 notify),但睡眠线程不能,它只能被中断。
  2. 等待(和通知)必须发生在基于监视器对象同步的块中,而睡眠则不需要。
  3. 虽然睡眠不会释放锁,但等待将释放调用其 wait 的对象的锁。

1
非常有用的信息,而在无限循环中使用Thread.sleep可能会导致低延迟时间内高CPU使用率。 - Amir Fo

5

我不知道为什么,但我正在编写的程序一直在使用计时器,并且其堆大小不断增加,一旦我将其更改为线程/休眠,问题就解决了。


9
计时器创建一个不断更新的任务队列。当计时器完成时,它可能不会立即被垃圾回收。因此,创建更多的计时器只会在堆上添加更多的对象。Thread.sleep() 只会暂停线程,因此内存开销极低。 - Darryl Gerrow

4

如果您的线程发生异常并被终止,那是一个问题。 但是TimerTask会处理它。它会运行,无论前一次运行是否失败。


4
使用Java线程和sleep方法来管理此任务存在一个关键性的问题。您正在使用while(true)无限循环并通过休眠线程来使其处于休眠状态。如果NewUploadServer.getInstance().checkAndUploadFiles();占用了一些同步资源,其他线程将无法访问这些资源,可能会发生饥饿现象,从而减慢整个应用程序的速度。这些错误很难诊断,因此最好预防它们的出现。
另一种方法是通过调用TimerTaskrun()方法来触发对您所关心的代码即NewUploadServer.getInstance().checkAndUploadFiles();的执行,同时允许其他线程在此期间使用资源。

16
我不理解这个论点。两种选择都会从线程中启动相同的方法,两种选择都在等待执行时在线程中睡眠。这两种选择之间不会有饥饿差异。 - satur9nine

2
我认为我理解了你的问题,我看到了非常相似的情况。我的计时器是循环的,有些是每30分钟一次,有些则是每隔几天一次。从我所读到的和所见到的评论来看,似乎垃圾回收永远不会运行,因为所有任务都没有完成。我认为当计时器处于睡眠状态时,垃圾回收应该会运行,但我没有看到它运行,根据文档也是如此。
我认为创建新线程可以完成并允许垃圾回收。
请有人证明我是错的,否则重写我继承的代码将会很痛苦。

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