Timer类原理解析,任务延迟执行

API

Timer类用于延迟任务的执行。

  • schedule(TimerTask task, **long **delay),指定延迟时间
  • schedule(TimerTask task, Date time),指定时间
  • schedule(TimerTask task, **long **delay, **long **period),指定延迟时间和重复周期
  • schedule(TimerTask task, Date firstTime, **long **period),指定初始时间和重复周期

源码解读

使用API好奇的地方主要有两点:

  1. 如何定时执行——如何保证准确的定时执行?这件事看起来几乎无法完成
  2. 多个任务,当前一个任务没执行完,后续任务如何定期执行。

遗憾的是,源码解读之后,发现Timer没有给出很好的答案。它大致上会把任务放入优先级队列中,执行时间为key,轮询取出时间最早的进行执行。如果前边任务执行时间过长,后续任务只会往后延迟。所以它的解法非常简单,既不保证精确的时间,也不保证任务一定按指定时间进行。
整体上,它会启动一个线程子类对象,该线程会执行轮询操作。

// Timer
public Timer(String name) {
    thread.setName(name);
    thread.start();
}
// TimerThread
public void run() {
    try {
        mainLoop();
    } finally {
        // Someone killed this Thread, behave as if Timer cancelled
        synchronized(queue) {
            newTasksMayBeScheduled = false;
            queue.clear();  // Eliminate obsolete references
        }
    }
}

Timer底层的队列是数组实现的最小堆,每次添加任务到队列中,会设置周期period,调度时间nextExecutionTime。为了确保线程安全性,它会对queue和task本身分别加锁。

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

    // Constrain value of period sufficiently to prevent numeric
    // overflow while still being effectively infinitely large.
    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);
        // 如果当前添加的任务为最早执行的任务,那么尝试唤醒轮询线程
        // 因为在轮询过程中,可能由于队列为空,等待在queue上
        if (queue.getMin() == task)
            queue.notify();
    }
}

轮询线程的核心方法:

private void mainLoop() {
    while (true) {
        try {
            TimerTask task;
            boolean taskFired;
            // 加锁轮询,如果queue为空,则等待
            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)) {
                        // 如果不需要周期执行,则remove,否则重新设定下一次时间
                        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) {
        }
    }
}

缺点

解读源码后,可以理解它的一些缺点:

1、首先Timer对调度的支持是基于绝对时间的,而不是相对时间,所以它对系统时间的改变非常敏感。

2、其次Timer线程是不会捕获异常的,如果TimerTask抛出的了未检查异常则会导致Timer线程终止,同时Timer也不会重新恢复线程的执行,他会错误的认为整个Timer线程都会取消。同时,已经被安排单尚未执行的TimerTask也不会再执行了,新的任务也不能被调度。故如果TimerTask抛出未检查的异常,Timer将会产生无法预料的行为

3、Timer在执行定时任务时只会创建一个线程任务,如果存在多个线程,若其中某个线程因为某种原因而导致线程任务执行时间过长,超过了两个任务的间隔时间,会导致下一个任务执行时间滞后
————————————————
来自《Java并发编程实践》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值