前言
本文隶属于专栏《100个问题搞定Java并发》,该专栏为笔者原创,引用请注明来源,不足和错误之处请在评论区帮忙指出,谢谢!
本专栏目录结构和参考文献请见100个问题搞定Java并发
正文
Timer 是什么?
Timer 类负责管理延迟任务(“在 100ms 后执行该任务”)以及周期任务(“每 10ms 执行次该任务”)。
Timer 的两大缺陷
然而, Timer 存在一些缺陷,因此应该考虑使用 ScheduledThreadPoolExecutor
来代替它。
可以通过 ScheduledThreadPoolExecutor 的构造函数或 newScheduledThreadpool 工厂方法来创建该类的对象。
Timer 在执行所有定时任务时只会创建一个线程。
如果某个任务的执行时间过长,那么将破坏其他 TimerTask 的定时精确性
。
例如某个周期 TimerTask 需要每 10ms 执行一次,而另一个 TimerTask 需要执行 40ms ,那么这个周期任务或者在 40ms 任务执行完成后快速连续地调用 4 次,或者彻底“丢失” 4 次调用(取决于它是基于固定速率来调度还是基于固定延时来调度)。
线程池能弥补这个缺陷,它可以提供多个线程来执行延时任务和周期任务。
Timer 的另一个问题是,如果 TimerTask 抛出了一个未检査的异常,那么 Timer 将表现出槽糕的行为。
Timer 线程并不捕获异常,因此当 TimerTask 抛出未检査的异常时将终止定时线程。
这种情况下, Timer 也不会恢复线程的执行,而是会错误地认为整个 Timer 都被取消了。
因此,已经被调度但尚未执行的 TimerTask 将不会再执行,新的任务也不能被调度。 (这个问题称之为“线程泄漏 [ Thread Leakage ] ”)
代码示例
下面的代码示例中给出了 Timer 中为什么会出现这种问题,以及如何使得试图提交 TimerTask 的调用者也出现问题。
import java.util.Timer;
import java.util.TimerTask;
import static java.util.concurrent.TimeUnit.SECONDS;
public class OutOfTime {
public static void main(String[] args) throws Exception {
Timer timer = new Timer();
timer.schedule(new Throwtask(), 1);
SECONDS.sleep(1);
timer.schedule(new Throwtask(), 1);
SECONDS.sleep(5);
}
static class Throwtask extends TimerTask {
public void run() {
throw new RuntimeException();
}
}
}
你可能认为程序会运行 6 秒后退出,但实际情况是运行 1 秒就结束了,并抛出了一个异常消息“ Timer already cancelled ”。
Exception in thread "Timer-0" java.lang.RuntimeException
at com.shockang.study.java.concurrent.scheduler.OutOfTime$Throwtask.run(OutOfTime.java:20)
at java.util.TimerThread.mainLoop(Timer.java:555)
at java.util.TimerThread.run(Timer.java:505)
Exception in thread "main" java.lang.IllegalStateException: Timer already cancelled.
at java.util.Timer.sched(Timer.java:397)
at java.util.Timer.schedule(Timer.java:193)
at com.shockang.study.java.concurrent.scheduler.OutOfTime.main(OutOfTime.java:13)
ScheduledThreadPoolExecutor 能正确处理这些表现出错误行为的任务。
在 Java5 . 0 或更高的 JDK 中,将很少使用 Timer 。
如果要构建自己的调度服务,那么可以使用 DelayQueue
,它实现了 BlockingQueue ,并为 ScheduledThreadPoolExecutor 提供调度功能。
Delayqueue 管理着一组 Delayed 对象。
每个 Delayed 对象都有一个相应的延迟时间:在 Delayqueue 中,只有某个元素逾期后,オ能从 DelayQueue 中执行 take 操作。
从 DelayQueue 中返回的对象将根据它们的延迟时间进行排序。