分析Timer实现以前,先复习下堆算法,以小顶堆为例。
堆
堆是一个完全二叉树,按照广度遍历的方式存储在数组里。
3 5 7 6 9 8
以上面这个堆为例。
实际存储是这样的:此处我们保留了数组的第一个元素,这样方便计算父子节点关系,有父子坐标关系:leftSon = parent2;rightSon = parent2+1;
---+--+--+--+--+--+-- nul| 3| 5| 7| 6| 9|8 ---+--+--+--+--+--+--
堆的操作主要分为两部:
1.往堆中插入数据,对应操作上浮,我们往堆里面增加一个元素时,先置到数组末尾。然后对比该节点和父节点的大小,如果子节点更小,则交换两者位置。继续将父节点和父父节点做上浮操作,直到堆顶。
2.去掉堆顶元素,对应操作下沉,去掉堆顶元素,此时变成一个无头堆,将数组最后的一个元素放到堆顶。可以看到此时的堆是不满足最小堆的,堆顶太大了。我们需要把堆顶下沉到它应该到的位置。那么问题来了,因为同一个父节点的两个子节点是无序的,所以此时两个子节点均可能是最小,要将三者之间的最小值置于父节点处,则有一次swap操作将父节点和其中一个子节点交换位置,继续对这个交换过后的子节点做下沉操作。直到下沉操作没有交换产生,则排序完毕。
Timer
下面来说Timer。Timer有一个内部线程thread,线程的生命周期始于Timer对象的构造函数,止于用户调用Timer对象的cancer()方法。
内部维护的queue是任务队列,可以通过Timer的schedule往队列放入创建的TimerTask实例来执行任务,调用其cancer()函数来取消任务,可以看到queue内部是个TimerTask的数组,实际结构是个以executionTime排序的小顶堆。
线程循环执行如下代码:
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) { } } }
作者:msrpp
链接:https://www.jianshu.com/p/76a25e3ce370