定时器——Timer源码详解

320 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第4天,点击查看活动详情

源码解读

TimerThread执行流程

当我们创建Timer类之后,就会启动一个TimerThread执行线程,线程主要执行了一个mainLoop()方法,大致流程如下:

源码:

private void mainLoop() {
    while (true) {
        try {
            TimerTask task;
            boolean taskFired;
            synchronized(queue) {
                //如果queue中没有要执行的任务并且还没有添加任务时,则先挂起等待
                while (queue.isEmpty() && newTasksMayBeScheduled)
                    queue.wait();
                
                //如果TimerThread被激活,queue里面还是没有任务
                //则该线程的无限循环,直到等有新任务出现
                if (queue.isEmpty())
                    break; 

                long currentTime, executionTime;
                //获取queue队列里面下一个要执行的任务(根据时间排序,也就是接下来最近要执行的任务)
                task = queue.getMin();
                synchronized(task.lock) {
                    //是否取消任务,通过TimerTask.cancen()可以取消
                    if (task.state == TimerTask.CANCELLED) {
                        queue.removeMin();
                        continue;  
                    }
                    currentTime = System.currentTimeMillis();
                    executionTime = task.nextExecutionTime;
                    //task.period==0表示这个任务只需要执行一次,这里就从queue里面移除了
                    if (taskFired = (executionTime<=currentTime)) {
                        if (task.period == 0) { 
                            queue.removeMin();
                            task.state = TimerTask.EXECUTED;
                        } else { 
                            //执行多次任务,需要重新计算下次执行时间点
                            queue.rescheduleMin(
                                task.period<0 ? currentTime   - task.period
                                : executionTime + task.period);
                        }
                    }
                }
                //如果任务的下次执行时间还没有到达,则挂起TimerThread线程executionTime - currentTime毫秒数
                //到达执行时间点再自动激活
                if (!taskFired) // Task hasn't yet fired; wait
                    queue.wait(executionTime - currentTime);
            }
            // 如果任务的下次执行时间到了,则执行任务
            // 注意:这里任务执行没有另起线程,还是在TimerThread线程执行的,所以当有任务在同时执行时会出现阻塞
             // 这里没有try catch异常,当TimerTask抛出异常会导致整个TimerThread跳出循环,从而导致Timer失效
            if (taskFired)  
                task.run();
        } catch(InterruptedException e) {
        }
    }
}

添加任务过程

添加任务的,不论是那种模式最终都是需要通过sched方法来添加到队列当中

private void sched(TimerTask task, long time, long period) {
    //校验时间
    if (time < 0)
        throw new IllegalArgumentException("Illegal execution time.");

    //约束period,防止过大
    if (Math.abs(period) > (Long.MAX_VALUE >> 1))
        period >>= 1;

    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)
            //唤醒TimerThread的等待
            queue.notify();
    }
}

注意:

period参数,fixed delay 传入的是一个小于负数进去,fixed rate传入了一个正数进去,所以在mainLoop方法中,重新设置下次执行时间点,加入了一些判断,用来区分2种模式

TaskQueue小顶堆

class TaskQueue {
    //
    //
    //
    private TimerTask[] queue = new TimerTask[128];
    
    //添加
    void add(TimerTask task) {
        // 扩容
        if (size + 1 == queue.length)
            queue = Arrays.copyOf(queue, 2*queue.length);

        queue[++size] = task;
        fixUp(size);
    }
    
    //移除
    void removeMin() {
        queue[1] = queue[size];
        //删除额外的引用,防止内存泄露
        queue[size--] = null;  
        fixDown(1);
    }
    
    //自上而下堆化
    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;
        }
    }
    
    //自下而上堆化
    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;
        }
    }
}

Priority queue represented as a balanced binary heap: the two children of queue[n] are queue[2n] and queue[2n+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.

队列的优先级用平衡二叉树来表示,queue[n]的2个孩子分别是queue[2*n]queue[2*n+1],优先级顺序按照nextExecutionTime排序,小的放在最前面(非空)。对于堆中的每个节点n,以及ndn.nextExecutionTime <= d.nextExecutionTime则此队列是基于小顶堆实现