最近遇到了一下Timer和TimerTask。以前没有认真看,这次正好有空就看看。略记如下。
先看下简单的例子:
public class TestTimer { private static final int MAX_COUNT = 10; private static volatile int count = 0; public static void main(String[] args) { Timer t = new Timer(); //t.schedule(new ETimer(), 0); t.schedule(new ETimer(),0, 2000); //Notice here, the count must be volatile !!!!! while(count < MAX_COUNT) { ; } t.cancel(); } private static class ETimer extends TimerTask { @Override public void run() { System.out.println(Thread.currentThread().getName() + ",count=" + count); count++; } } }
例子很简单。新建一个Timer,然后需要重新写一个Task类继承TimeTask。调用Schedule() 方法就可以了。结果如下:
Timer-0,count=0
Timer-0,count=1
Timer-0,count=2
Timer-0,count=3
Timer-0,count=4
Timer-0,count=5
Timer-0,count=6
Timer-0,count=7
Timer-0,count=8
Timer-0,count=9
TimeTask里面必须要Override的方法是run,猜测应该是跟Runnable有关。点进去看看。
public abstract class TimerTask implements Runnable
猜测没错。
那么这个run方法是怎么被调用的呢?肯定是Timer在schedule里面调用的。
看看Timer的结构:
public class Timer { /** * 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 TaskQueue queue = new TaskQueue(); /** * The timer thread. */ private TimerThread thread = new TimerThread(queue);
哦哦,里面有个Queue保存着Task。
开了一个线程TimerThread,并把queue赋给了它。
class TaskQueue { /** * Priority queue represented as a balanced binary heap: the two children * of queue[n] are queue[2*n] and queue[2*n+1]. The priority queue is * ordered on the nextExecutionTime field: The TimerTask with the lowest * nextExecutionTime is in queue[1] (assuming the queue is nonempty). For * each node n in the heap, and each descendant of n, d, * n.nextExecutionTime <= d.nextExecutionTime. */ private TimerTask[] queue = new TimerTask[128]; /** * The number of tasks in the priority queue. (The tasks are stored in * queue[1] up to queue[size]). */ private int size = 0;
这个Queue是用数组实现的,且数组的0号index不存放TimerTask。
void add(TimerTask task) { // Grow backing store if necessary if (size + 1 == queue.length) queue = Arrays.copyOf(queue, 2*queue.length); queue[++size] = task; fixUp(size); }
这个add方法就是往数组里面加一个TimerTask吧。慢着,这个fixUp是干嘛的?
除了fixUp还有一个fixDown哈哈。
堆, 对,就是堆!
那就是把最前面的那个TimerTask放在堆的顶部了。下次直接取前面的那个就好啦。
再看看TimerThread:
class TimerThread extends Thread { /** * This flag is set to false by the reaper to inform us that there * are no more live references to our Timer object. Once this flag * is true and there are no more tasks in our queue, there is no * work left for us to do, so we terminate gracefully. Note that * this field is protected by queue's monitor! */ boolean newTasksMayBeScheduled = true; //一个很重要的标志位。Timer的cancel方法跟这个有关。 /** * Our Timer's queue. We store this reference in preference to * a reference to the Timer so the reference graph remains acyclic. * Otherwise, the Timer would never be garbage-collected and this * thread would never go away. */ 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 } } } /** * 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里面,取堆顶的元素,然后看看它是否需要执行。如果需要执行,设定好时间,到时候执行
task.run();
看看Timer的cancel方法是怎么影响这个的
public void cancel() { synchronized(queue) { thread.newTasksMayBeScheduled = false; queue.clear(); queue.notify(); // In case queue was already empty. } }
将标志位置0,然后清空queue。
再到mainLoop,可以跳到break, 跳出最外面的while循环(注意是run方法中的while, 不是mainLoop中的while),TimerThread结束。因此整个方法也结束了。
在Timer里面, 有单次的也有循环的,通过参数个数不同而很容易区分。但是有两个特别相近:
public void schedule(TimerTask task, long delay, long period) { if (delay < 0) throw new IllegalArgumentException("Negative delay."); if (period <= 0) throw new IllegalArgumentException("Non-positive period."); sched(task, System.currentTimeMillis()+delay, -period);//NOTICE 最后的参数是一个负数 } public void scheduleAtFixedRate(TimerTask task, long delay, long period) { if (delay < 0) throw new IllegalArgumentException("Negative delay."); if (period <= 0) throw new IllegalArgumentException("Non-positive period."); sched(task, System.currentTimeMillis()+delay, period);//NOTICE:最后的参数是一个正数 }
private void sched(TimerTask task, long time, long period) { if (time < 0) throw new IllegalArgumentException("Illegal execution time."); 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(); } }
两个方法都调用了上面的sched方法。主要是往Queue里面加入Task。注意在:
task.period = period;
处,period的正反性是不变的.
回想上面TimerThread类的mainLoop方法里面是不是有关于period的正负性质的判断?
queue.rescheduleMin( task.period<0 ? currentTime - task.period : executionTime + task.period);
看看rescheduleMin方法:
void rescheduleMin(long newTime) { queue[1].nextExecutionTime = newTime; fixDown(1); }
可以看到一个设置的是在当前时间之后的period的时间, 一个是设置的在执行时间后的时间。
那有什么区别呢?在于TimeTask的任务是不是能够在间隔时间内执行完。schedule方法和scheduleAtFixedRate主要是为了解决--如果执行时间超过了间隔时间,该如何重新设置定时器的问题。
关于这两个的比较一个比较好的博客是:
http://www.ewdna.com/2011/12/java-timerschedulescheduleatfixedrate.html
也可以顺带着看下:
http://www.ewdna.com/2011/12/java-timer.html
结合例子和源码可以看出更深层次的不同之处。
后续有空可以仔细看看内部的细节如锁机制等等。