前言

本文隶属于专栏《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 中返回的对象将根据它们的延迟时间进行排序。

上一篇 下一篇