对于java来说,最简单的定时任务可以使用jdk自带的java.util.Timer来实现。java.util.Timer类是通过调度一个java.util.TimerTask的任务并让这个任务依照某种频度执行一次或重复执行。
java.util.Timer
从java.util.Timer的源码可以看到,Timer类定义了两个私有变量queue(java.util.TaskQueue类型)和thread(java.util.TimerThread.TimerThread类型)。
java.util.TaskQueue
对于Timer的私有变量queue,是java.util.TaskQueue类型的。TaskQueue是一个定时器任务队列,一个TimerTask的优先级队列。
java.util.TimerThread
对于Timer的私有变量thread,类型是继承自Thread类的java.util.TimerThread类类型。这是Timer类的任务运行线程,参数是就是上面提到的java.util.TaskQueue类型的任务队列。
java.util.TimerTask
上面提到了java.util.TimerTask类,TimerTask类实现了Runnable接口,待运行的任务置于run()中。在构造定时任务的时候,从TimerTask继承并实现run方法。
工作原理
当Timer类型的对象调用schedule或scheduleAtFixedRate等方法时,把TimerTask类型的任务对象作为参数传递进去,从Timer的内部调用方法中sched能够看出,sched方法中须要操作TaskQueue队列把这个任务对象维护到Timerd的任务队列queue中,而TimerThread线程启动之后相同使用这个队列,为了保证线程的安全,使用synchronized来控制对queue的操作。
核心函数
void java.util.Timer.schedule(TimerTask task, long delay):多长时间(毫秒)后运行任务
void java.util.Timer.schedule(TimerTask task, Date time):设定某个时间运行任务
void java.util.Timer.schedule(TimerTask task, long delay, long period):delay时间后開始运行任务,并每隔period时间调用任务一次。
void java.util.Timer.schedule(TimerTask task, Date firstTime, long period):第一次在指定firstTime时间点运行任务,之后每隔period时间调用任务一次。
void java.util.Timer.scheduleAtFixedRate(TimerTask task, long delay, long period):delay时间后開始运行任务。并每隔period时间调用任务一次。
void java.util.Timer.scheduleAtFixedRate(TimerTask task, Date firstTime, long period):第一次在指定firstTime时间点运行任务。之后每隔period时间调用任务一次。
void java.util.Timer.cancel():终止该Timer
boolean java.util.TimerTask.cancel():终止该TimerTask
schedule和scheduleAtFixedRate的区别
schedule()方法更注重保持间隔时间的稳定:保障每隔period时间可调用一次。
scheduleAtFixedRate()方法更注重保持运行频率的稳定:保障多次调用的频率趋近于period时间。假设某一次调用时间大于period,下一次就会尽量小于period。以保障频率接近于period。
Timer的缺陷及注意事项
1、由于执行任务的线程只有一个,所以如果某个任务的执行时间过长,那么将破坏其他任务的定时精确性。如一个任务每1秒执行一次,而另一个任务执行一次需要5秒,那么如果是固定速率的任务,那么会在5秒这个任务执行完成后连续执行5次,而固定延迟的任务将丢失4次执行。
2、如果执行某个任务过程中抛出了异常,那么执行线程将会终止,导致Timer中的其他任务也不能再执行。
3、Timer使用的是绝对时间,即是某个时间点,所以它执行依赖系统的时间,如果系统时间修改了的话,将导致任务可能不会被执行。
一个简单的例子
该例子模拟了Timer缺陷中的第一种情况,可以切换schedule和scheduleAtFixedRate对比测试效果,其他缺陷可自行验证。