Timer & TimerTask 作为java的任务调度器之一,了解其内部原理有助于我们更好的理解它与其他调度器之间的异同。
- TimerTask
- Timer及其调度过程
- 总结
TimerTask作为被调度任务的抽象类,继承于Thread,但是没有实现run方法,而是留给我们根据具体业务需求来实现。
cancel()
/** *设置一个任务的状态位为cancelled,但是并没有从任务队列中删除。 *而是继续存在于任务队列,调用purge();方法后会将无效的任务(cancelled)删除。 **/
public boolean cancel() {
synchronized(lock) {
boolean result = (state == SCHEDULED);
state = CANCELLED;
return result;
}
}
scheduledExecutionTime()
/** *任务最近一次将要被执行的时间。 *period之所以可以小于0,因为在Timer中设置调度方式式时有两种方案。 **/
public long scheduledExecutionTime() {
synchronized(lock) {
return (period < 0 ? nextExecutionTime + period
: nextExecutionTime - period);
}
}
schedule(TimerTask task, long delay): 当前系统时间延迟delay后执行,只执行一次。
schedule(TimerTask task, Date time): 在指定的Date执行,只执行一次。
schedule(TimerTask task, long delay, long period): 当前系统时间延迟delay执行,后续每隔period执行一次,循环执行。
schedule(TimerTask task, Date firstTime, long period): 第一次执行的时间为Date,后续每隔period执行一次,循环执行。
scheduleAtFixedRate(TimerTask task, long delay, long period): 同样是当前系统时间延迟delay后执行,每隔period执行一次,循环执行。但是在计算下次执行时间上与schedule()有所不同,比如最近一次执行时间是X,period为5,Timer调度存在3毫秒的延迟,那么由schedule计算出来的下次执行时间为X+5+3,而由scheduleAtFixedRate计算出来的时间仍为X+period,这个下次执行时间会影响Task在任务队列中的位置。
scheduleAtFixedRate(TimerTask task, Date firstTime, long period): 理解同上。
sched(TimerTask task, long time, long period): 该函数是以上所有调度方式的内部执行函数,接下来会详细介绍。
/** * TaskQueue queue = new TaskQueue(); * TaskQueue是Timer的一个内部类,其主要成员为:TimerTask[] queue = new TimerTask[128]; * 以及提供对该数组的一些操作。 * **/
private void sched(TimerTask task, long time, long period) {
if (time < 0)
throw new IllegalArgumentException("Illegal execution time.");
if (Math.abs(period) > (Long.MAX_VALUE >> 1))
period >>= 1;
synchronized(queue) {
if (!thread.newTasksMayBeScheduled)
throw new IllegalStateException("Timer already cancelled.");
synchronized(task.lock) {
if (task.state != TimerTask.VIRGIN)
throw new IllegalStateException(
"Task already scheduled or cancelled");
task.nextExecutionTime = time;
task.period = period;
task.state = TimerTask.SCHEDULED;
}
queue.add(task);
if (queue.getMin() == task)
queue.notify();
}
}
该函数的意义是用户提交一个任务后,设置好下次执行的时间,时间片(period)大小,任务状态后,加入任务队列,实质是放入一个任务数组。
//实现细节
void add(TimerTask task) {
// Grow backing store if necessary
if (size + 1 == queue.length)
queue = Arrays.copyOf(queue, 2*queue.length);
queue[++size] = task;
fixUp(size);
}
private void fixUp(int k) {
while (k > 1) {
int j = k >> 1;
if (queue[j].nextExecutionTime <= queue[k].nextExecutionTime)
break;
TimerTask tmp = queue[j]; queue[j] = queue[k]; queue[k] = tmp;
k = j;
}
}
这里解释下fixUp(),它其实完成的是一个排序功能,但是不是绝对意义上的有序,用相对有序更为恰当,它的目的只是将新的任务放到一个合适的位置。
k>>1相当于k/2,这样处理的好处是提高对于大量堆积任务的排序性能,因为他极大的减少了需要排序的次数,同时也保证了一定程度的有序性。试想,每次有新的任务进来时,都要完成一次绝对排序,对于整体性能影响是比较大的。
Timer实际被调用的两个构造函数
private final TimerThread thread = new TimerThread(queue);
public Timer(String name) {
thread.setName(name);
thread.start();
}
public Timer(String name, boolean isDaemon) {
thread.setName(name);
thread.setDaemon(isDaemon);
thread.start();
}
TimerThread是Timer的一个内部类,继承Thread,是Timer中真正执行调度动作的线程。但是需要注意的是他是单线程,而非线程池,也就是说,Timer对于任务是串行执行的。
接下来看下TimerThread的run方法
public void run() {
try {
mainLoop();
} finally {
synchronized(queue) {
newTasksMayBeScheduled = false;
queue.clear();
}
}
}
private void mainLoop() {
while (true) {
try {
TimerTask task;
boolean taskFired;
synchronized(queue) {
while (queue.isEmpty() && newTasksMayBeScheduled)
queue.wait();
if (queue.isEmpty())
break;
long currentTime, executionTime;
task = queue.getMin();
synchronized(task.lock) {
if (task.state == TimerTask.CANCELLED) {
queue.removeMin();
continue;
}
currentTime = System.currentTimeMillis();
executionTime = task.nextExecutionTime;
if (taskFired = (executionTime<=currentTime)) {
if (task.period == 0) {
queue.removeMin();
task.state = TimerTask.EXECUTED;
} else {
queue.rescheduleMin(
task.period<0 ? currentTime - task.period
: executionTime + task.period);
}
}
}
if (!taskFired)
queue.wait(executionTime - currentTime);
}
if (taskFired)
task.run();
} catch(InterruptedException e) {
}
}
}
}
由上可见,主要的任务轮询发生在mainloopd的while(true)循环中。
首先进行 while (queue.isEmpty() && newTasksMayBeScheduled)循环,第一次运行时,queue必然为空,且newTasksMayBeScheduled初始值为true。所以线程第一次执行时一定会走到 queue.wait();这里,它会因为任务队列为空而进入等待,相应的,我们可以看到在sched()中提交任务后会有notify()动作来激活该线程。
那么继续往下看,为何在激活后又需要判断队列是否为空呢?
我们看一段代码
public void cancel() {
synchronized(queue) {
thread.newTasksMayBeScheduled = false;
queue.clear();
queue.notify(); // In case queue was already empty.
}
}
Timer的cancle()操作,同样会激活线程,所以需要再次检查队列是否为空。而且需要注意的是,再次检查,如果队列为空,则跳出循环,也就是说TimerThread执行结束,调度完毕。
若队列不为空,进行queue.getMin()操作,可以认为是取了TimerTask[]数组中的”task head“元素。
检查取到的任务的状态,是否为cancel,若是则从队列中删除,继续循环部分。
之前提到TimerTask中的cancel()只是更改了标志位,实际的移除在是调用removeMin()来删除的。
接下来判断该任务是否满足执行条件。
currentTime = System.currentTimeMillis();
executionTime = task.nextExecutionTime;
if (taskFired = (executionTime<=currentTime)) {
if (task.period == 0) {
queue.removeMin();
task.state = TimerTask.EXECUTED;
} else {
queue.rescheduleMin(
task.period<0 ? currentTime - task.period
: executionTime + task.period);
}
}
意思是取下次执行时间与系统当前时间做比较,如果下次执行时间晚于系统当前时间,则说明该任务不满足执行条件。
反正满足执行条件,对于满足执行条件的任务,需要判断其时间片period的大小,如果是0,说明任务只需要执行一次。反正任务是循环任务。
对于period为0的任务,会将其从任务队列中移除,并将任务的标志位置为已执行。
对于需要循环执行的任务,会计算出下一次任务将要执行的时间并对相应字段进行设置,之前提到的调度方式有中的schedule与scheduleAtFixedRate的差别在这里也被体现出来,就参数而言,schedule中的period为大于0,scheduleAtFixedRate中的period小于0。
最后,对于满足执行条件的任务调用其TimerTask的run方法。
对于不满足执行条件的任务进行定时等待,等待时间为executionTime - currentTime。
1.Timer & TimerTask的基本原理
2.Timer的缺点,它始终是单线程的,所以在处理任务时,可能会因为一个长时间的任务而导致其他任务无法被调度。
3.Timer中对于任务队列的处理方式(相对有序等思想)