从Timer中学习优先队列的实现

Timer是Java定时器的实现,用来调度定时执行的任务和执行一次的任务,就像JavaScript的setIntervalsetTimeout的意思,它也可以作为后台程序(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()

最重要的两个是fixDownfixUp,表示从queue[k]节点位置开始demotingpromoting

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;
        }
    }

promotingdemoting简单一点,只需要比较queue[k]queue[j]两个节点,然后做交换即可。

TaskQueue other methods

通过fixUpfixDown的方法可以保证整个优先队列的关系,保证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
附:测试代码


Honwhy
7k 声望96 粉丝

神兽党有福了