做项目很多时候会用到定时任务,比如在深夜,流量较小的时候,做一些统计工作。早上定时发送邮件,更新数据库等。这里可以用Java的Timer或线程池实现。Timer可以实现,不过Timer存在一些问题。他起一个单线程,如果有异常产生,线程将退出,整个定时任务就失败。
下面是一个Timer实现的定时任务Demo,会向控制台每隔一秒输出Do work...
1 importjava.util.Date;2 importjava.util.Timer;3 importjava.util.TimerTask;4
5 /**
6 * Created by gxf on 2017/6/21.7 */
8 public classTestTimer {9 public static voidmain(String[] args) {10 Timer timer = newTimer();11 Task task = newTask();12 timer.schedule(task, new Date(), 1000);13 }14 }15
16 class Task extendsTimerTask{17
18 @Override19 public voidrun() {20 System.out.println("Do work...");21 }22 }
控制台输出
Do work...
Do work...
Do work...
Do work...
我们将进入JDK源码分析一下,Timer原理
Timer源码
public classTimer {/*** 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 final TaskQueue queue = newTaskQueue();/*** The timer thread.*/
private final TimerThread thread = new TimerThread(queue);
这里可以看出,有一个队列(其实是个最小堆),和一个线程对象
我们在看一下Timer的构造函数
/*** Creates a new timer. The associated thread does not
* {@linkplainThread#setDaemon run as a daemon}.*/
publicTimer() {this("Timer-" +serialNumber());
}
这里调用了有参构造函数,进入查看
/*** Creates a new timer whose associated thread has the specified name.
* The associated thread does not
* {@linkplainThread#setDaemon run as a daemon}.
*
*@paramname the name of the associated thread
*@throwsNullPointerException if {@codename} is null
*@since1.5*/
publicTimer(String name) {
thread.setName(name);
thread.start();
}
这里可以看到,起了一个线程
ok,我们再看一下,TimerTask这个类
/*** A task that can be scheduled for one-time or repeated execution by a Timer.
*
*@authorJosh Bloch
*@seeTimer
*@since1.3*/
public abstract class TimerTask implements Runnable {
虽然代码不多,也不贴完,这里看出,是一个实现了Runable接口的类,也就是说可以放到线程中运行的任务
这里就清楚了,Timer是一个线程,TimerTask是一个Runable实现类,那只要提交TimerTask对象就可以运行任务了。
public void schedule(TimerTask task, Date firstTime, longperiod) {if (period <= 0)throw new IllegalArgumentException("Non-positive period.");
sched(task, firstTime.getTime(),-period);
}
进入Timer shed(task, firstTime, period)
private void sched(TimerTask task, long time, longperiod) {if (time < 0)throw new IllegalArgumentException("Illegal execution time.");//Constrain value of period sufficiently to prevent numeric//overflow while still being effectively infinitely large.
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 newIllegalStateException("Task already scheduled or cancelled");
task.nextExecutionTime=time;
task.period=period;
task.state=TimerTask.SCHEDULED;
}
queue.add(task);if (queue.getMin() ==task)
queue.notify();
}
}
这里主要是queue.add(task)将任务放到最小堆里面,并queue.notity()唤醒在等待的线程
那么我们进入Timer类的TimerThread对象查看run方法,因为Timer类里面有个TimerThread 对象是一个线程
public voidrun() {try{
mainLoop();
}finally{//Someone killed this Thread, behave as if Timer cancelled
synchronized(queue) {
newTasksMayBeScheduled= false;
queue.clear();//Eliminate obsolete references
}
}
}
这里可以看出,在执行一个mainLoop()循环,进入这个循环
/*** The main timer loop. (See class comment.)*/
private voidmainLoop() {while (true) {try{
TimerTask task;booleantaskFired;synchronized(queue) {//Wait for queue to become non-empty
while (queue.isEmpty() &&newTasksMayBeScheduled)
queue.wait();if(queue.isEmpty())break; //Queue is empty and will forever remain; die//Queue nonempty; look at first evt and do the right thing
longcurrentTime, executionTime;
task=queue.getMin();synchronized(task.lock) {if (task.state ==TimerTask.CANCELLED) {
queue.removeMin();continue; //No action required, poll queue again
}
currentTime=System.currentTimeMillis();
executionTime=task.nextExecutionTime;if (taskFired = (executionTime<=currentTime)) {if (task.period == 0) { //Non-repeating, remove
queue.removeMin();
task.state=TimerTask.EXECUTED;
}else { //Repeating task, reschedule
queue.rescheduleMin(
task.period<0 ? currentTime -task.period
: executionTime+task.period);
}
}
}if (!taskFired) //Task hasn‘t yet fired; wait
queue.wait(executionTime -currentTime);
}if (taskFired) //Task fired; run it, holding no locks
task.run();
}catch(InterruptedException e) {
}
}
这里忘了说明,TimerTask是按nextExecutionTime进行堆排序的。每次取堆中nextExecutionTime和当前系统时间进行比较,如果当前时间大于nextExecutionTime则执行,如果是单次任务,会将任务从最小堆,移除。否则,更新nextExecutionTime的值
至此,Timer定时任务原理基本理解,单线程 + 最小堆 + 不断轮询