对于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类型)。
public class Timer {
/**
* The timer task queue. This data structure is shared with the timer
* thread. The timer produces tasks, via its various schedule calls,
* and the timer thread consumes, executing timer tasks as appropriate,
* and removing them from the queue when they're obsolete.
*/
private TaskQueue queue = new TaskQueue();
/**
* The timer thread.
*/
private TimerThread thread = new TimerThread(queue);
java.util.TaskQueue
对于Timer的私有变量queue,是java.util.TaskQueue类型的。TaskQueue是一个定时器任务队列,一个TimerTask的优先级队列。
class TaskQueue {
/**
* Priority queue represented as a balanced binary heap: the two children
* of queue[n] are queue[2*n] and queue[2*n+1]. The priority queue is
* ordered on the nextExecutionTime field: The TimerTask with the lowest
* nextExecutionTime is in queue[1] (assuming the queue is nonempty). For
* each node n in the heap, and each descendant of n, d,
* n.nextExecutionTime <= d.nextExecutionTime.
*/
private TimerTask[] queue = new TimerTask[128];
/**
* The number of tasks in the priority queue. (The tasks are stored in
* queue[1] up to queue[size]).
*/
private int size = 0;
java.util.TimerThread
对于Timer的私有变量thread,类型是继承自Thread类的java.util.TimerThread类类型。这是Timer类的任务运行线程,参数是就是上面提到的java.util.TaskQueue类型的任务队列。
class TimerThread extends Thread {
/**
* This flag is set to false by the reaper to inform us that there
* are no more live references to our Timer object. Once this flag
* is true and there are no more tasks in our queue, there is no
* work left for us to do, so we terminate gracefully. Note that
* this field is protected by queue's monitor!
*/
boolean newTasksMayBeScheduled = true;
/**
* Our Timer's queue. We store this reference in preference to
* a reference to the Timer so the reference graph remains acyclic.
* Otherwise, the Timer would never be garbage-collected and this
* thread would never go away.
*/
private TaskQueue queue;
java.util.TimerTask
上面提到了java.util.TimerTask类,TimerTask类实现了Runnable接口,待运行的任务置于run()中。在构造定时任务的时候,从TimerTask继承并实现run方法。
public abstract class TimerTask implements Runnable {
/**
* This object is used to control access to the TimerTask internals.
*/
final Object lock = new Object();
//...
/**
* The action to be performed by this timer task.
*/
public abstract void 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对比测试效果,其他缺陷可自行验证。
package cn.lovecto.test.timer;
import java.util.Timer;
import java.util.TimerTask;
public class TimerJob {
private static Timer timer = new Timer(TimerJob.class.getName(), true);
static {
//延迟1000ms执行程序,每1000ms执行一次,此处了换成scheduleAtFixedRate进行对比
timer.schedule(new TimerTask() {
@Override
public void run() {
doSomething1();
}
}, 1000, 1000);
//从当前时间执行首次,每1000执行一次,此处了换成scheduleAtFixedRate进行对比
timer.schedule(new TimerTask() {
@Override
public void run() {
doSomething2();
}
}, 1000, 5000);
}
private static void doSomething1() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("doSomething1");
}
private static void doSomething2() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("doSomething2");
}
public static void main(String[] args) throws InterruptedException {
//循环查看执行效果
while (true) {
Thread.sleep(100);
}
}
}