详解Timer定时器

时间:2021-03-18 23:26:51
一、概述
Timer类的全名为java.util.Timer,直接继承了java.lang.Object类,它主要提供定时器的功能。
Timer是一种线程用于调度任务的工具,被调度的任务可以将来在一个后台线程中执行。这些任务可以被调度为只执行一次,或者以一定的时间间隔重复执行。
相应地,每个Timer对象都是一个单个的后台线程,可以用于顺序地执行所有的定时器任务。定时器任务应当迅速地完成。如果一个定时器任务需要过多的时间才能完成,那么它就会霸占定时器的任务执行线程。这样可能会依次延迟后续任务的执行,当(以及如果)违规的任务最终执行结束时,后续的任务可能会挤在一起,快速连续地执行。
当最后一个指向某个Timer对象的引用消失,并且所有未完成的任务都执行结束之后,这个定时器的任务执行线程会从容地终止(然后受垃圾收集控制)。但是,这有可能要花费任意长的时间才会发生。在默认情况下,这个任务执行线程不会作为一个守护线程运行,因此它能够保证应用程序不会终止。如果一个调用者想要快速地终止一个定时器的任务执行线程,那么这个调用者应当调用定时器的cancel方法。
如果这个定时器的任务执行线程意外终止了,例如,因为调用了它的stop方法,所以任何进一步使用这个定时器对某个任务进行调度的企图都将会导致一个IllegalStateException异常,就好像调用了这个定时器的cancel方法一样。
这个类是线程安全的:多个线程可以共享一个单个的Timer对象,而不需要外部的同步机制。
这个类并不能提供实时性保证:它使用Object.wait(long)方法来调度任务。
Java 5.0引入了java.util.concurrent包,其中有一个并发工具是ScheduledThreadPoolExecutor,它是一种能够以一个给定的速率或延时重复执行任务的线程池。它是一个更加通用和高效的Timer/TimerTask组合的替代方案,因为它允许多个服务线程接受各种不同的时间单位,并且不需要定义TimerTask的子类(只需要实现Runnable接口)。将ScheduledThreadPoolExecutor配置为一个线程,则会使它等同于Timer。
实现注意事项:这个类可以适用于大量的并发调度任务(可以应付上千个并发调度任务)。在内部,它使用一个二叉堆来表示它的任务队列,因此调度一个任务的开销为O(log n),其中的n是并发调度任务的总数。
实现注意事项:所有的构造器都会启动一个定时器线程。
二、构造器1. Timer()
创建一个新的定时器。与其关联的线程没有作为一个守护线程运行。

2. Timer(boolean isDaemon)
创建一个新的定时器,与其关联的线程可以被指定作为一个守护线程运行。如果这个定时器将会被用于调度执行重复的“维护活动”,那么就需要使用一个守护线程。只要应用程序还在运行,就一定会执行这些“维护活动”,但是不应当延长应用程序的生存期。

参数:
isDaemon - 如果关联的线程应当作为一个守护线程运行,那么就设为true。

3. Timer(String name)
创建一个新的定时器,与其关联的线程可以有指定的名称。关联的线程没有作为一个守护线程运行。

参数:
name - 关联线程的名称。

抛出异常:
NullPointerException - 如果参数name为null值。

4. Timer(String name, boolean isDaemon)
创建一个新的定时器,与其关联的线程既可以有指定的名称,也可以被指定作为一个守护线程运行。

参数:
name - 关联线程的名称。
isDaemon - 如果关联的线程应当作为一个守护线程运行,那么就设为true。

抛出异常:
NullPointerException - 如果参数name为null值。

三、方法1. void cancel()
终止这个定时器,丢弃所有当前已调度的任务。不要干扰当前正在执行的任务(如果存在的话)。一旦终止了一个定时器,它的执行线程就会从容地终止,并且就不能用它来继续调度任务了。

注意,在一个计时器任务的run方法内部调用这个方法,而这个定时器任务又是由这个定时器触发的,这样完全能保证正在执行的任务总是这个定时器执行的最后一个任务。

这个方法可能会被重复调用,第二次和后续的调用都不会产生任何效果。

2. int purge()
从定时器的任务队列中移除所有已取消的任务。调用这个方法不会对定时器的行为产生任何影响,但是会从队列中消除指向已取消任务的引用。如果没有任何指向这些任务的外部引用,那么它们就符合垃圾收集的条件。

大多数程序不需要调用这个方法。它被设计用于一些稀有的应用程序之中,这些程序可能会取消大量的任务。调用这个方法就是以时间换取空间:这个方法的运行时间和n + c log n成正比,其中的n是队列中的任务总数,c是取消任务的总数。

注意,可允许在一个由这个定时器调度的任务的内部调用这个方法。

返回值:
从队列中移除的任务总数。

3. void schedule(TimerTask task, Date time)
在指定的时间调度指定的任务执行。如果指定的时间已经过去了,那么任务会被调度立即执行。

参数:
task - 待调度的任务
time - 上述任务将要执行的时间

抛出异常:
IllegalArgumentException - 如果time.getTime()的返回值是负数。
IllegalStateException - 如果已经调度或取消了这个任务,或者取消了这个定时器,或者终止了这个定时器线程。
NullPointerException - 如果参数task或time是null值。

4. void schedule(TimerTask task, Date firstTime, long period)
调度指定的任务以固定的延时重复执行,在指定的时间开始执行。后续的任务会以近似的固定时间间隔重复执行,由指定的周期分隔。

在固定的延时内执行,每次执行都会相对于上一次执行的真实执行时间进行调度。如果某次执行由于任意原因延迟了(例如,垃圾收集或其他后台活动),那么后续的执行也同样将会延迟。在长期运行时,执行频率通常将会比指定周期的倒数略微慢一点(假设系统时钟相关的Object.wait(long)方法是精确的)。综上所述,如果首次调度的时间已经过去了,那么就会将任务调度为立即执行。

固定延时执行适合于循环执行活动,这些活动需要“平滑性”。换句话说,它适合于在短时间内保证执行频率精确性的活动,但是不适合于需要长时间定期运行的活动。这包含了大多数的动画任务,例如,以固定时间间隔闪烁一个光标。它还包含了用于响应人机交互操作而需要定期执行活动的任务,例如,只要一直按下某个按键就会自动地重复输出对应的字符。

参数:
task - 待调度的任务
firstTime - 首次执行任务的时间
period - 连续任务执行之间的间隔时间,以毫秒为单位

抛出异常:
IllegalArgumentException - 如果firstTime.getTime() < 0,或period <= 0。
IllegalStateException - 如果已经调度或取消了这个任务,或者取消了这个定时器,或者终止了这个定时器线程。
NullPointerException - 如果参数task或firstTime是null值。

5. void schedule(TimerTask task, long delay)
调度指定的任务在指定的延时之后执行。

参数:
task - 待调度的任务。
delay - 任务执行之前的延时,以毫秒为单位。

抛出异常:
IllegalArgumentException - 如果参数delay是负数,或者delay + System.currentTimeMillis()是负数。
IllegalStateException - 如果已经调度或取消了这个任务,或者取消了这个定时器,或者终止了这个定时器线程。
NullPointerException - 如果参数task是null值。

6. void schedule(TimerTask task, long delay, long period)
调度指定的任务以固定的延时重复执行,在指定的延时之后开始执行。后续的任务会以近似的固定时间间隔重复执行,由指定的周期分隔。

在固定的延时内执行,每次执行都会相对于上一次执行的真实执行时间进行调度。如果某次执行由于任意原因延迟了(例如,垃圾收集或其他后台活动),那么后续的执行也同样将会延迟。在长期运行时,执行频率通常将会比指定周期的倒数略微慢一点(假设系统时钟相关的Object.wait(long)方法是精确的)。

固定延时执行适合于循环执行活动,这些活动需要“平滑性”。换句话说,它适合于在短时间内保证执行频率精确性的活动,但是不适合于需要长时间定期运行的活动。这包含了大多数的动画任务,例如,以固定时间间隔闪烁一个光标。它还包含了用于响应人机交互操作而需要定期执行活动的任务,例如,只要一直按下某个按键就会自动地重复输出对应的字符。

参数:
task - 待调度的任务
delay - 任务执行之前的延时,以毫秒为单位。
period - 连续任务执行之间的间隔时间,以毫秒为单位

抛出异常:
IllegalArgumentException - 如果参数delay小于0,或delay +  System.currentTimeMillis()小于0,或period小于等于0。
IllegalStateException - 如果已经调度或取消了这个任务,或者取消了这个定时器,或者终止了这个定时器线程。
NullPointerException - 如果参数task是null值。

7. void scheduleAtFixedRate(TimerTask task, Date firstTime, long period)
调度指定的任务以固定的速率重复指定,在指定的时间开始执行。后续的任务会以近似的固定时间间隔重复执行,由指定的周期分隔。

在固定速率的执行中,每次执行都会相对于首次执行的调度执行时间进行调度。如果某次执行由于任意原因而延迟了(例如,垃圾收集或其他后台活动),那么两次或更多次的执行将会快速连续地发生,以便于“赶上”速率。在长期运行中,执行频率将精确等于指定周期的倒数(假设系统时钟相关的Object.wait(long)方法是精确的)。综上所述,如果首次调度的时间已经过去了,那么任何“错过的”执行都将会被调度为马上执行,以便于“赶上”速率。

固定速率执行适合于重复执行的活动,这些活动对于绝对时间比较敏感,例如,每小时准点地响铃,或者在每天的特定时间运行已经调度的维护任务。有些重复执行的活动会执行固定数量的任务,它的执行总时间很重要,这个方法也适合于这样的活动。例如,一个倒数计时器每秒钟会滴答一次,持续十秒钟。最后,固定速率执行适合于调度多个重复执行的定时器任务,这些定时器任务必须彼此之间相对保持同步。

参数:
task - 待调度的任务。
firstTime - 任务首次执行的时间。
period - 连续任务执行之间的间隔时间,以毫秒为单位。

抛出异常:
IllegalArgumentException - 如果firstTime.getTime() < 0或period < 0。
IllegalStateException - 如果已经调度或取消了这个任务,或者取消了这个定时器,或者终止了这个定时器线程。
NullPointerException - 如果参数task或firstTime是null值。

8. void scheduleAtFixedRate(TimerTask task, long delay, long period)
调度指定的任务以固定的速率重复执行,在指定的延时之后开始执行。后续的执行会以近似固定的时间间隔发生,由指定的周期分隔。

在固定速率的执行中,每次执行都会相对于首次执行的调度执行时间进行调度。如果某次执行由于任意原因而延迟了(例如,垃圾收集或其他后台活动),那么两次或更多次的执行将会快速连续地发生,以便于“赶上”速率。在长期运行中,执行频率将精确等于指定周期的倒数(假设系统时钟相关的Object.wait(long)方法是精确的)。

固定速率执行适合于重复执行的活动,这些活动对于绝对时间比较敏感,例如,每小时准点地响铃,或者在每天的特定时间运行已经调度的维护任务。有些重复执行的活动会执行固定数量的任务,它的执行总时间很重要,这个方法也适合于这样的活动。例如,一个倒数计时器每秒钟会滴答一次,持续十秒钟。最后,固定速率执行适合于调度多个重复执行的定时器任务,这些定时器任务必须彼此之间相对保持同步。
参数:task - 待调度的任务。delay - 任务执行之前的延时,以毫秒为单位。period - 连续任务执行之间的间隔时间,以毫秒为单位。
抛出异常:IllegalArgumentException - 如果参数delay < 0,或者delay + System.currentTimeMillis() < 0,或者period <= 0。
IllegalStateException - 如果已经调度或取消了这个任务,或者取消了这个定时器,或者终止了这个定时器线程。
NullPointerException - 如果参数task是null值。