Java定时器与ExecutorService有什么区别?

307

我有一段代码,使用java.util.Timer来安排任务。我看到ExecutorService也可以完成相同的任务。所以我的问题是,你是否使用过TimerExecutorService来安排任务,其中一个的好处是什么?

另外,我想知道是否有人使用Timer类并遇到了ExecutorService解决的问题。


3
如果您需要更多功能的东西,请查看quartz。它提供了更多作业控制,包括类似于cron的调度、集群感知调度、对作业的个性化控制(例如一次只运行一个、依赖关系等概念)。 - Tim
6个回答

355
根据《Java并发编程实战》:
  • Timer 对系统时钟的更改敏感,ScheduledThreadPoolExecutor 则不会。
  • Timer 只有一个执行线程,因此长时间运行的任务可能会延迟其他任务。 ScheduledThreadPoolExecutor 可以配置任意数量的线程。此外,如果需要的话(通过提供 ThreadFactory),您可以完全控制创建的线程。
  • TimerTask 中抛出的运行时异常会终止该线程,从而使 Timer 失效:-(...即预定的任务将不再运行。 ScheduledThreadExecutor 不仅捕获运行时异常,而且如果需要,它还可以让您处理这些异常(通过覆盖来自 ThreadPoolExecutorafterExecute 方法)。抛出异常的任务将被取消,但其他任务将继续运行。

如果可以使用 ScheduledThreadExecutor 而不是 Timer,请这样做。

还有一件事...虽然 Java 1.4 库中没有 ScheduledThreadExecutor,但是有一个 JSR 166 的后备方案(java.util.concurrent)可用于 Java 1.2、1.3、1.4,其中包含 ScheduledThreadExecutor 类。


2
另一个优点是,如果您喜欢lambda表达式,Timer就不能使用它们。ScheduledExecutorService可以接受lambda表达式,因为它所需的参数是“Runnable”而不是“TimerTask”。 - jlguenego

68

如果您可以使用,很难想到不使用Java 5执行器框架的理由。调用:

ScheduledExecutorService ex = Executors.newSingleThreadScheduledExecutor();

使用 ScheduledExecutorService 将为您提供类似于 Timer 的功能(即它将是单线程的),但其访问可能会稍微更加可扩展(在内部,它使用并发结构而不是与 Timer 类一样的完全同步)。 使用 ScheduledExecutorService 还具有以下优点:

  • 如果需要,您可以自定义它(请参阅 newScheduledThreadPoolExecutor()ScheduledThreadPoolExecutor 类)
  • 'one off' 执行可以返回结果

我能想到坚持使用 Timer 的唯一原因是:

  • 它在 Java 5 之前可用
  • J2ME 中提供了类似的类,这可能使您的应用程序移植更容易(但在这种情况下添加一个公共的抽象层也不是非常困难)

1
使用TimerTask的另一个原因可能是它提供了scheduledExecutionTime()方法,而在ScheduledExecutorService中似乎没有相应的方法。 - Rohit Agarwal
4
另外需要说明的是,我是在2017年撰写这条评论的,J2ME已经消失了,已经死亡。 - msangel
1
Java的计时器类很糟糕。 - JohnyTex

30

ExecutorService是较新且更通用的。定时器只是定期运行您为其安排的内容的线程。

ExecutorService可以是线程池,甚至可以分布在集群中的其他系统上,执行诸如一次性批处理等操作...

只需查看每个提供的功能即可决定使用哪一个。


10

以下是关于ScheduledThreadPoolExecutor的Oracle文档页面:

ScheduledThreadPoolExecutor 是一个线程池执行器,可以在给定延迟后调度命令运行,或定期执行。当需要多个工作线程时,或需要 ThreadPoolExecutor(该类扩展了此类)的额外灵活性或功能时,此类比 Timer 更可取。

当您有多个工作线程时,ExecutorService/ThreadPoolExecutorScheduledThreadPoolExecutor 是明显的选择。

ExecutorService 相对于 Timer 的优点:

  1. Timer无法利用可用的CPU核心,不像使用ForkJoinPoolExecutorService的多个任务可以。
  2. ExecutorService提供了协同API,如果您需要协调多个任务之间的操作。假设您必须提交N个工作任务并等待它们全部完成,您可以轻松使用invokeAll API实现。如果要使用多个Timer任务实现相同的操作,那将不会简单。
  3. ThreadPoolExecutor提供更好的线程生命周期管理API。

    线程池解决了两个不同的问题:由于减少了每个任务调用开销,因此在执行大量异步任务时通常提供了改进的性能,并且它们提供了一种控制和管理资源(包括线程)的手段,这些资源在执行任务集合时消耗。每个ThreadPoolExecutor还维护一些基本统计信息,例如已完成的任务数

    优点如下:

    a. 您可以创建/管理/控制线程的生命周期和优化线程创建成本开销

    b. 您可以控制任务的处理方式(工作窃取、ForkJoinPool、invokeAll)等。

    c. 您可以监视线程的进度和健康状况

    d. 提供更好的异常处理机制


6

我有时候更喜欢使用 Timer 而不是 Executors.newSingleThreadScheduledExecutor(),原因在于当我需要定时器在守护线程上执行时,代码会更加简洁。

比较

private final ThreadFactory threadFactory = new ThreadFactory() {
    public Thread newThread(Runnable r) {
        Thread t = new Thread(r);
        t.setDaemon(true);
        return t;
    }
};
private final ScheduledExecutorService timer = Executors.newSingleThreadScheduledExecutor(threadFactory); 

使用

private final Timer timer = new Timer(true);

当我不需要执行器服务的强大功能时,我会这样做。


2

我曾经遇到一个计时器的问题,为了解决它,我用ScheduledExecutorService进行了替换。

问题在于计时器依赖于系统时间,每次改变时间都会影响应用程序的运行。所以我用ScheduledExecutorService替换了计时器,现在它正常工作。


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