java.util.Timer:是一个实用工具类,该类用来调度一个线程(schedule a thread),使它可以在将来某一时刻执行。 Java的Timer类可以调度一个任务运行一次,或定期循环运行。 Timer tasks should complete quickly. 即定时器中的操作要尽可能花费短的时间。
java.util.TimerTask:是一个抽象类,它实现了Runnable接口。我们需要扩展该类以便创建自己的TimerTask,这个TimerTask可以被Timer调度。
注意:默认, task执行线程不是daemon线程, 任务执行完,主线程(或其他启动定时器的线程)结束时,task线程并没有结束。如果调用者想要快速终止计时器的任务执行线程,调用者应该调用timer.cancel()
方法。
public void timerTest() {
Timer myTimer = new Timer();
myTimer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("1s后运行");
myTimer.cancel(); // 需要手动cancel
}
}, 1000);
}
Timer类是线程安全的,多进程不需要外部同步机制就可以共享同一个Timer对象。创建Timer对象,会创建一个java.util.TaskQueue实例,在执行定时任务时,将taskqueue对象作为锁,在指定时间间隔添加任务,在任何时刻只能有一个线程执行TimerTask。
Timer类使用对象的wait和notify方法来调度任务。
查看Timer源代码:
// TaskQueue队列,内部就是一个TimerTask[]数组
private final TaskQueue queue = new TaskQueue();
// Timer内部维持一个叫TimerThread的线程,传递TaskQueue队列
private final TimerThread thread = new TimerThread(queue);
// 创建Timer即启动线程
public Timer(String name) {
thread.setName(name);
thread.start();// 启动线程,后面有分析TimerThread的run方法
}
public void schedule(TimerTask task, long delay) {
if (delay < 0)
throw new IllegalArgumentException("Negative delay.");
sched(task, System.currentTimeMillis()+delay, 0);
}
// 核心调度方法,time表示执行的绝对时间
private void sched(TimerTask task, long time, long period) {
...
// timer对象中的queue作为锁
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为调度状态
task.state = TimerTask.SCHEDULED;
}
// 将当前待执行的task添加到队列中
queue.add(task);
// 队列中取出的head task为当前task
if (queue.getMin() == task)
// 在任何时刻只能有一个线程执行TimerTask
queue.notify();
}
}
其中Timer中启动的TimerThread的run方法:
private TaskQueue queue;
TimerThread(TaskQueue queue) {
this.queue = queue;
}
public void run() {
try {
mainLoop();
} finally {
// Someone killed this Thread, behave as if Timer cancelled
synchronized(queue) {
newTasksMayBeScheduled = false;
queue.clear(); // Eliminate obsolete references
}
}
}
private void mainLoop() {
while (true) {
try {
TimerTask task;
boolean taskFired;
synchronized(queue) {
// 如果queue队列为空,则将线程阻塞,等待task
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
long currentTime, executionTime;
// 队列中获取task
task = queue.getMin();
synchronized(task.lock) {
// 此处说明可以通过cancel()设置终止task的执行,但TimerThread并没有终止
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(); // 执行task的run方法!
} catch(InterruptedException e) {
}
}
}
TimerTask的调度流程:由以上分析可知,当我们创建一个Timer时,同时内部创建了一个TimerThread线程,并启动它,该线程会不断扫描从Timer传递进来的task队列,如果为空,则wait()阻塞该线程;当timer调用shedule方法的时候,将传递的task添加到队列中,同时调用queue.notify()
方法唤醒TimerThread线程,则从队列中取出task根据给定的等待时间wait等待,等待完成后执行task.run();
启动任务。(这种结构可以应用到简单的爬虫中)
如何终止Timer线程:
默认情况下,创建的timer线程会一直执行,一般通过下面的方式来终止timer线程:
下面给出一个Timer执行多个task的简单案例:
public void multileTasksShareOneTimer() {
Timer myTimer = new Timer();
TimerTask task1 = new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() +" 1s后执行task1");
//myTimer.cancel();
}
};
TimerTask task2 = new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() +" 2s后执行task2");
//myTimer.cancel();
}
};
myTimer.schedule(task1, 1000);
// task2.cancel();
myTimer.schedule(task2, 2000);
}
运行输出:注意此时Timer的TimerThread并没有结束,因为在mainLoop()等待task而wait进入阻塞状态!
如果设置了task2.cancel();则调度执行task2会抛出异常:
定时器实际使用的场景还是很多的,比如下面的,在每天零晨备份数据库,等等。
/**
* 每天0晨备份数据库
*/
@SuppressWarnings("deprecation")
public void backupDatabase() {
Timer timer = new Timer();
TimerTask task = new TimerTask() {
@Override
public void run() {
// 备份数据库
System.out.println("数据库备份...");
// other operation
}
};
Date firstTime = new Date();
firstTime.setHours(0);
firstTime.setMinutes(0);
firstTime.setSeconds(0);
long oneDayPeriod = 24 * 60 * 60 * 1000;
timer.scheduleAtFixedRate(task, firstTime, oneDayPeriod);
// timer.schedule(task, firstTime, oneDayPeriod);
}
最后:如果是简单的定时调度,使用Timer就够了,如果复杂的调度任务,可以考虑使用Quartz
转载请注明出处:Java多线程总结(3)— Timer 和 TimerTask深入分析