最近看了一下java定时调度Timer的源码,源码简单、清晰,结构清除,对于了解任务调度入门有帮助,因此在此对源码阅读总结一下。

综述

java 的Timer是一个定时任务调度控制器, 在java 并行库中的ScheduledExecutorService面世前,java中主要通过Timer应用实现定时时任务调度, 在Timer的定时任务调度框架中主要由Timer, Taskqueue, TimerThread, TimerTask组成,Timer是调度器对外的接口,提供对外的任务添加以及调度中心管理接口,TaskQueue是任务下次执行时间的优先级队列,该优先级队列是小顶堆结构实现的。TimerThread是任务执行的主体 ,每次从任务队列中取出下一个需要执行的任务,在该任务下次执行时间到来时开始执行,TimerTask是一个定时调度任务的抽象类,通过state对任务状态管理。

类关系图

如下图所示,为Timer中各个模块的类图
java Timer定时任务调度原理

总体时序图

TimerTimerThreadTaskQueue任务执行线程开始作业获取下一个需要执行的任务返回任务loop[ Every minute ]task状态判定添加任务TimerTimerThreadTaskQueue

模块详解

Timer

Timer类在构造函数里面会启动任务执行线程,在之后,用户就可以通过调用schedule接口将任务添加进任务队列,等待线程调用,因此Timer的主要任务就是添加定时任务,Timer提供了几个schedule函数,各个schedule函数在参数有不同,主要是做参数验证,而各个schedule对任务的添加最终归拢到了一个函数shed。

	@param time 开始执行时间   
	@param period 任务间隔 
    private void sched(TimerTask task, long time, long period) {
        synchronized(queue) {
            synchronized(task.lock) {
                if (task.state != TimerTask.VIRGIN)
                    throw new IllegalStateException
                //任务调度初始化
                task.nextExecutionTime = time;
                task.period = period;
                task.state = TimerTask.SCHEDULED;
            }
            //添加task到队列中
            queue.add(task);
            //如果通知队列有任务,与TimerThread的queue wait相对应
            if (queue.getMin() == task)
                queue.notify();
        }
    }

定时任务中,通过period控制任务时间点,period的不同值有不同含义,0表示一次性任务,大于0表示时间以上次执行开始时间为基准,小于0表示以上次执行完毕时间为基准。举个例子,一个任务执行时间为1s,如果period=5,则该任务的执行时间点为:0,5,10, 15;而如果period=-5,则执行时间点为:0,6,12,18。
由上面的代码知道,Timer是线程安全的,对资源queue,以及task的操作都加了锁。

TimerThread

TimerThread是任务的调度执行主体,线程循环从队列中获取最近需要执行的任务,等待该任务执行时间点执行任务

private void mainLoop() {
        while (true) {
                synchronized(queue) {
                    // Wait for queue to become non-empty
                    while (queue.isEmpty() && newTasksMayBeScheduled)
                        queue.wait();
                    task = queue.getMin();
                    synchronized(task.lock) {
                    	//任务已经被取消,从队列中删除
                        if (task.state == TimerTask.CANCELLED) {
                            queue.removeMin();
                            continue;  // No action required, poll queue again
                        }
                        //当前任务是否可以运行
                        if (taskFired = (executionTime<=currentTime)) {
                        	//一次性任务,移除
                            if (task.period == 0) {
                                remove task
                            } else { 
                            //安排下次执行时间点
                                reschedule task
                            }
                        }
                    }
                    //还未到达任务执行时间点,需要等待,或者被唤醒
                    if (!taskFired)
                        queue.wait(executionTime - currentTime);
                }
                //执行任务
                if (taskFired)  
                    task.run();
            } catch(InterruptedException e) {
            	//中断退出调度
            }
        }
    }

TaskQueue

TaskQueue是用栈实现的优先级队列,队列中获取下一个执行的任务,是从栈中获取开始执行时间最早的任务,执行完成以后,该任务要么被移除,要么被重新安排调度计划加入队列中,无论是删除还是重新安排加入都会调整栈。该栈的实现是以一个从1开始索引的数组实现的,满足n元素的子节点是2n,2n+1,根据栈的规则,满足p[n]>p[2n] 以及p[n]>p[2n+1]。每次添加元素,在队尾添加,然后向上迭代调整,每次从栈顶取元素,删除栈顶元素是将队尾元素放到栈顶,而后迭代向下调整。对于该栈的维护,核心是向下调整以及向上调整,向下调整将栈顶元素放置还是位置;向上调整,将队尾元素调整到合适位置。下面是向上调整以及向下调整代码:
向下调整

    private void fixDown(int k) {
        int j;
        //直至当前节点小于两个子节点,或者已经是叶子节点
        while ((j = k << 1) <= size && j > 0) {
            if (j < size &&
                queue[j].nextExecutionTime > queue[j+1].nextExecutionTime)
                j++; // j indexes smallest kid
            if (queue[k].nextExecutionTime <= queue[j].nextExecutionTime)
                break;
            TimerTask tmp = queue[j];  queue[j] = queue[k]; queue[k] = tmp;
            k = j;
        }
    }

向上调整

        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;
        }

总结

Timer的虽然代码简单,但是里面却涵盖了信号量、锁、线程、栈、优先级队列以及调度等知识,而且代码中在栈的实现里面有些巧妙的处理,如通过移位取代除2的操作,因此代码有很大阅读价值

相关文章: