import java.util.Timer;
import java.util.TimerTask;
public class TimerTest {
public static void main(String[] args) throws InterruptedException {
//创建一个定时器
Timer timer = new Timer();
//创建任务内容
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread().getId());
}
};
//设置多久开始运行,隔多久运行
timer.schedule(timerTask, 1000, 1000);
//主线程休眠一段时间
Thread.sleep(11100);
//手动停止定时器
timer.cancel();
}
}
原理:
1、先来看下Timer
类的大概描述:
import java.util.TaskQueue;
import java.util.TimerThread;
public class Timer {
private final TaskQueue queue = new TaskQueue();
private final TimerThread thread = new TimerThread(queue);
private final Object threadReaper = new Object() {
protected void finalize() throws Throwable {
synchronized (queue) {
thread.newTasksMayBeScheduled = false;
queue.notify(); // In case queue is empty.
}
}
};
//other things...
}
可以看到,Timer
定时器内部包含了个TaskQueue
队列,专门用来存储添加进来的任务。
同时内部还包含了个class TimerThread extends Thread
,TimerThread是一个新的线程,所有队列里的任务,都是通过这个线程调用执行。
2、再来看下new Timer()
做了啥,下面是构造方法:
public Timer(String name, boolean isDaemon) {
thread.setName(name);
thread.setDaemon(isDaemon);
thread.start();
}
重点在于thread.start()
,也就是说只要创建了Timer对象,Timer内部的TimerThread线程就已经启动。
3、再来看public abstract class TimerTask implements Runnable
,可知TimerTask是一个可以被线程调用的Runnable。
当执行timer.schedule(timerTask, 1000, 1000);
,代码逻辑如下:
void schedule(TimerTask task, long time, long period) {
//do SomeThings
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.add(task);
把任务加到队列
第二个是queue.getMin() == task
判断队列头是不是当前任务,如果是直接让队列所在的线程跑起来:queue.notify();
就是去唤醒TimerThread线程
4、TimerThread线程为什么需要唤醒呢?且看下面的TimerThread线程的run方法:
class TimerThread extends Thread {
public void run() {
try {
mainLoop();
} finally {
// Someone killed this Thread, behave as if Timer cancelled
synchronized(queue) {
newTasksMayBeScheduled = false;
queue.clear(); // Eliminate obsolete references
}
}
}
}
深入看mainLoop()
方法:
void mainLoop() {
//简化后的代码
while (true) {
TimerTask task;
boolean taskFired;
synchronized (queue) {
// Wait for queue to become non-empty
while (queue.isEmpty() && newTasksMayBeScheduled){
queue.wait(); //队列空了,让线程等待
}
if (queue.isEmpty()){
break; // 跳出循环,
}
// Queue nonempty; look at first evt and do the right thing
long currentTime, executionTime;
task = queue.getMin();
synchronized (task.lock) {
//判断当前task是否轮到执行时间
taskFired = true;
}
}
if (taskFired) // Task fired; run it, holding no locks
task.run();
}
}
上面说到new Timer()
立马就启动了线程thread.start()
,启动线程就会执行run方法,run方法就会调用mainLoop
方法,看到里面判断如果队列为空while (queue.isEmpty())
则让线程等待queue.wait()
。
所以每次添加任务进来,如果轮到当前任务执行,那么首先就是唤醒线程,然后线程继续执行队列中的任务。
因为这while (true)
是一个无限循环的去执行队列任务的方法,只要任务队列不为空,则一定会按顺序被单线程TimerThread
调用任务的run方法。
其实,Timer的创建,任务添加到队列,都是在主线程完成的。但是队列中任务的执行,却是在TimerThread.run()
方法中执行,所以就是TimerThread线程跑了。
5、如果定时任务队列为空,Timer也不会自动停止,需要手动停止:
如上面代码所示:timer.cancel();,它的实现如下:
public void cancel() {
synchronized(queue) {
thread.newTasksMayBeScheduled = false;
queue.clear();
queue.notify(); // In case queue was already empty.
}
}
对比mainLoop方法可以知道:设置thread.newTasksMayBeScheduled = false;
之后,已经不满足while (queue.isEmpty() && newTasksMayBeScheduled)
,但是满足if (queue.isEmpty())
,所以直接break
跳出循环了,因此能够结束timer。
那如果不自己执行cancel方法呢?看上面timer类的一个属性threadReaper:
private final Object threadReaper = new Object() {
protected void finalize() throws Throwable {
synchronized (queue) {
thread.newTasksMayBeScheduled = false;
queue.notify(); // In case queue is empty.
}
}
};
创建一个对象并重写finalize()
方法,目的就是,当主线程中没有对Timer的引用时,当垃圾回收发生时,就会回收这个Timer对象,从而回收threadReaper对象,从而执行其finalize
方法,注意到finalize方法和cancel方法类似,都thread.newTasksMayBeScheduled = false;
,如果队列为空,那么就会直接跳出循环,从而结束timer定时器线程的生命周期。