从Timer中学习优先队列的实现
Timer是Java定时器的实现,用来调度定时执行的任务和执行一次的任务,就像JavaScript的setInterval
和setTimeout
的意思,它也可以作为后台程序(Daemon)运行。
Timer
Timer调度的实现是通过TimerThread辅助类来实现的,在构造Timer实例的时候TimerThread就开始运行了;TimerThread需要从队列(TaskQueue)中获得需要被执行的任务(TimerTask),这是一个优先队列,TimeTask的executionTime(TimerTask设置要执行的时间Date.getTime()形式表示的)越小的越优先执行。
TimerThread如何调度
略
TaskQueue data structure
TaskQueue实现了优先队列的数据结构,内部是一个数组,数组内容实际上是从下标1开始填充的;它其实是用balanced binary heap
来表示的,设父节点是queue[n]
,则它的两个字节点分别是queue[2*n]
和queue[2*n+1]
这个数据结构的API方法包括:
- add(T object)
- getMin()
- removeMin()
- fixDown(int k)
- fixUp(int k)
- heapify()
最重要的两个是fixDown
和fixUp
,表示从queue[k]
节点位置开始demoting
或promoting
。
TaskQueue.fixDown
假设要操作的节点是queue[k]
,那么它的子节点分别是queue[j]
和queue[j+1]
,j=k*2
,对queue[k]
做demoting
处理,
private void fixDown(int k) {
int j;
while ((j = k << 1) <= size && j > 0) {
if (j < size &&
queue[j].nextExecutionTime > queue[j+1].nextExecutionTime)
j++; // j indexes smallest kid
if (queue[k].nextExecutionTime <= queue[j].nextExecutionTime)
break;
TimerTask tmp = queue[j]; queue[j] = queue[k]; queue[k] = tmp;
k = j;
}
}
1.首先比较两个子节点选出executionTime更小的那个,
2.如果右子节点的executionTime更小,则j++
自增,这样就相当于选择了右节点(下次fixDown的位置从这里开始)
3.然后父节点和选中的更小executionTime子节点比较
4.如果父节点的executionTime更小,则交换父节点和这个子节点
那么为什么要执行2呢
之前,
之后,
如果是queue[j]
变成了父节点就会破坏queue[n]<=queue[2*n+1]
的关系。
然后就是一直fixDown到最后一个节点queue[size]
。
我们可以思考下这个方法的时间复杂度是不是O(logn)
。
TaskQueue.fixUp
假设要操作的节点是queue[k]
,那么它的父节点分别是queue[j]
,j=k/2
,对queue[k]
做promoting
处理,
private void fixUp(int k) {
while (k > 1) {
int j = k >> 1;
if (queue[j].nextExecutionTime <= queue[k].nextExecutionTime)
break;
TimerTask tmp = queue[j]; queue[j] = queue[k]; queue[k] = tmp;
k = j;
}
}
promoting
比demoting
简单一点,只需要比较queue[k]
和queue[j]
两个节点,然后做交换即可。
TaskQueue other methods
通过fixUp
和fixDown
的方法可以保证整个优先队列的关系,保证queue[1]
的executionTime是最小的。
1.heapify()作用是构建堆,可以从{0,q[1],q[2],...,q[size]}
的数组构建堆,来表示优先队列。
void heapify() {
for (int i = size/2; i >= 1; i--)
fixDown(i);
}
从中间下标到1做fixDown。
2.add(T object),往数组中添加元素,重新构建堆
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);
}
不过需要判断数组空间是否有剩余,没有则扩展数组,并拷贝原来的数组元素,最后对queue[size]
节点,也是新元素做fixUp。
3.getMin() 直接获得queue[1]
元素
4.removeMin() 将queue[size]
节点替换queue[1]
,然后对queue[1]
做fixDown。
总结
这个TaskQueue的优先队列的实现代码是比较清晰的,重要方法的时间复杂度也可以算优秀。
阅读完这部分代码后或许可以进一步阅读PriorityQueue
。
附:测试代码
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。