Timer线程介绍
Timer 是一个用来将将要执行的任务放在后台线程执行的工具。被执行性的任务可以执行一次就结束了,也可以通过设定的时间间隔来周期性的执行。
Timer的基本使用
创建一个 Timer 对象,调用其 schedule() 方法传递一个TimerTask ,同时设定多长时间后开始执行及执行周期。这样,就可以执行任务了。代码如下:
public void test() {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
while (true) {
System.err.println(Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, 10000);
}
Timer 的源码分析
Timer使用涉及的三个相关类
- TimerThread 真正执行任务的线程,Thread 的子类
- TimerTask 被执行的任务,可以看做一个Runnable 对象
- TaskQueue 存放 TimerTask 的类,封装了一个 TimerTask 数组用来保存待执行的任务
创建Timer对象
Timer timer = new Timer();
分析 Timer 的源码,可以看到,Timer 对象分装了 TaskQueue对象和 TimerThread对象
public class Timer {
private final TaskQueue queue = new TaskQueue();
private final TimerThread thread = new TimerThread(queue);
//省略其他代码
}
在通过构造方法创建对象时,最中会调用如下构造方法,可以看出,对象创建过程中就开启了线程。线程的 start() 方法最终会调用其 run() 方法。
public Timer(String name, boolean isDaemon) {
thread.setName(name);
thread.setDaemon(isDaemon);
thread.start();
}
由于 TimerThread 继承了 Thread ,同时重写了父类的 run() 方法,所以最终会执行 TimerThread 的 run() 方法。
class TimerThread{
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
}
}
}
}
run() 方法会调用 mainLoop() 方法,这才是任务的核心代码。
/**
* The main timer loop. (See class comment.)
*/
private void mainLoop() {
while (true) {
try {
TimerTask task;
boolean taskFired;
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
long currentTime, 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) {
}
}
}
我们可以看到,mainLoop() 方法里面有个死循环,同时对queue对象进行同步加锁 synchronized ,先判断任务列表 queue 是否为空,如果为空,则调用 queue.wait() 使当线程进行休眠状态。所以说,如果不向 queue 中添加任务,那么该线程将永远处于休眠状态。那么如何添加任务呢? timer.schedule() 方法的作用就出来了。
添加任务
timer.schedule(TimerTask,long) ,最终会调用 sched() 方法。
private void sched(TimerTask task, long time, long period) {
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 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();
}
}
添加任务过程中先进行各种条件的判断,同样也对 queue 对象进行了加锁,调用 queue.add(task) 将任务添加到任务列表中。那么任务添加了,如何执行呢? queue.notify() 会唤醒其他享有该对象的锁的的线程继续执行任务。我们可以看到另一个对该对象加锁的线程正是TimerThread ,而此时 TimerThread 又调用了 wait() 方法处于等待状态,此时 notify() 方法会通知它继续执行任务。
执行任务
再次回到TimerThread的mainLoop() 方法中,当线程被唤醒后,继续执行字后的代码。同时进行条件判断,如果被唤醒后线程列表还是为空,那么线程到此结束。
从线程列表中取出线程,查看其执行时间及是否需要周期性的执行。进行一系列操作后,开始执行任务,调用 task.run() 方法,这样,我们创建的 TimerTask 对象的 run() 方法就会执行了。
当我们不需要执行任务后,需要调用 timer 的 cancel() 方法取消任务执行。
public void cancel() {
synchronized(queue) {
thread.newTasksMayBeScheduled = false;
queue.clear();
queue.notify(); // In case queue was already empty.
}
}
使用注意
- 通常我们在Android中会周期性的执行一些任务,当这些任务有和界面进行交互的功能时,我们退出界面前一定要先取消任务,不然会引发异常,导致程序挂掉。
- 当项目中存在大量的定期任务时,可以考虑使用Quartz