java基础之Timer原理讲解及使用说明

简介

 在java中一个完整定时任务需要由Timer、TimerTask两个类来配合完成。 API中是这样定义他们的,Timer:一种工具,线程用其安排以后在后台线程中执行的任务。可安排任务执行一次,或者定期重复执行。由TimerTask:Timer 安排为一次执行或重复执行的任务。我们可以这样理解Timer是一种定时器工具,用来在一个后台线程计划执行指定任务,而TimerTask一个抽象类,它的子类代表一个可以被Timer计划的任务。

也可以这样理解:

Timer和TimeTask是java基础中实现定时程序的两个核心类。

A、TimerTask是一个抽象类,内部实现了Runnable接口,同时定义了任务的几个状态;

B、Timer类来负责调度继承TimerTask的具体任务,可实现定时或者指定周期运行

Timer类

130943_WWwF_3027745.png

Timer有两个核心的属性,一个是TaskQueue对象,用于存储任务队列,一个是TimerThread,用于执行队列中的任务。

public class Timer {
    /**
     * The timer task queue.  This data structure is shared with the timer
     * thread.  The timer produces tasks, via its various schedule calls,
     * and the timer thread consumes, executing timer tasks as appropriate,
     * and removing them from the queue when they're obsolete.
     */
    private final TaskQueue queue = new TaskQueue();

    /**
     * The timer thread.
     */
    private final TimerThread thread = new TimerThread(queue);

Timer有三部分组成:

任务单元,任务队列,任务调度器,从下图能够非常直观的看出几个类直接的关系。

2a9b6887-8983-3402-8307-4e2af8b648d7.jpg

TaskQueue任务队列的管理:

一个具有优先级的时间任务队列,通过下次执行时间来进行排序。实际上就是一个数组,数据里面放着了TimeTask对象。TaskQueue 是Timer的内部类

/**
 * Adds a new task to the priority queue.
 */
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);
}
class TaskQueue {
    /**
     * Priority queue represented as a balanced binary heap: the two children
     * of queue[n] are queue[2*n] and queue[2*n+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.
     */
    private TimerTask[] queue = new TimerTask[128];

上面是TaskQueue中的add方法,添加新的TimeTask的时候,首先判断是否超出了数组长度,初始数组是128,如果超过,就将数组长度加倍。

最后执行fixUp,重新将数组进行排序,TimeTask中nextExecutionTime小的排在数组的最前面,即最近一个要执行的定时任务。

/**
 * Establishes the heap invariant (described above) assuming the heap
 * satisfies the invariant except possibly for the leaf-node indexed by k
 * (which may have a nextExecutionTime less than its parent's).
 *
 * This method functions by "promoting" queue[k] up the hierarchy
 * (by swapping it with its parent) repeatedly until queue[k]'s
 * nextExecutionTime is greater than or equal to that of its parent.
 */
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;
    }
}

Timer中重载了这个方法schedule,用来实现不同场景的任务调度,每一个schedule方法中都是用了这个方法sched

/**
 * Schedule the specified timer task for execution at the specified
 * time with the specified period, in milliseconds.  If period is
 * positive, the task is scheduled for repeated execution; if period is
 * zero, the task is scheduled for one-time execution. Time is specified
 * in Date.getTime() format.  This method checks timer state, task state,
 * and initial execution time, but not period.
 *
 * @throws IllegalArgumentException if <tt>time</tt> is negative.
 * @throws IllegalStateException if task was already scheduled or
 *         cancelled, timer was cancelled, or timer thread terminated.
 * @throws NullPointerException if {@code task} is null
 */
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);
        if (queue.getMin() == task)
            queue.notify();
    }
}

从上述代码中可以看出,首先锁住任务队列,如果task符合条件,则直接将任务加入到任务队列中,并设置好任务下次执行时间以及执行周期并设置状态为可以被调度。 

7c1ee3a1-6d85-39fb-bf76-a920ad641a99.jpg

在工具类Timer中,提供了四个构造方法,每个构造方法都启动了计时器线程,同时Timer类可以保证多个线程可以共享单个Timer对象而无需进行外部同步,所以Timer类是线程安全的。但是由于每一个Timer对象对应的是单个后台线程,用于顺序执行所有的计时器任务,一般情况下我们的线程任务执行所消耗的时间应该非常短,但是由于特殊情况导致某个定时器任务执行的时间太长,那么他就会“独占”计时器的任务执行线程,其后的所有线程都必须等待它执行完,这就会延迟后续任务的执行,使这些任务堆积在一起,具体情况我们后面分析。

      当程序初始化完成Timer后,定时任务就会按照我们设定的时间去执行,Timer提供了schedule方法,该方法有多中重载方式来适应不同的情况,如下:      

schedule(TimerTask task, Date time):安排在指定的时间执行指定的任务。

schedule(TimerTask task, Date firstTime, long period) :安排指定的任务在指定的时间开始进行重复的固定延迟执行。

schedule(TimerTask task, long delay) :安排在指定延迟后执行指定的任务。

schedule(TimerTask task, long delay, long period) :安排指定的任务从指定的延迟后开始进行重复的固定延迟执行。

      同时也重载了scheduleAtFixedRate方法,scheduleAtFixedRate方法与schedule相同,只不过他们的侧重点不同,区别后面分析。      

scheduleAtFixedRate(TimerTask task, Date firstTime, long period):安排指定的任务在指定的时间开始进行重复的固定速率执行。

scheduleAtFixedRate(TimerTask task, long delay, long period):安排指定的任务在指定的延迟后开始进行重复的固定速率执行。

Timer的缺陷

Timer计时器可以定时(指定时间执行任务)、延迟(延迟5秒执行任务)、周期性地执行任务(每隔个1秒执行任务),但是,Timer存在一些缺陷。首先Timer对调度的支持是基于绝对时间的,而不是相对时间,所以它对系统时间的改变非常敏感。其次Timer线程是不会捕获异常的,如果TimerTask抛出的了未检查异常则会导致Timer线程终止,同时Timer也不会重新恢复线程的执行,他会错误的认为整个Timer线程都会取消。同时,已经被安排单尚未执行的TimerTask也不会再执行了,新的任务也不能被调度。故如果TimerTask抛出未检查的异常,Timer将会产生无法预料的行为。

1、Timer管理时间延迟缺陷

前面Timer在执行定时任务时只会创建一个线程任务,如果存在多个线程,若其中某个线程因为某种原因而导致线程任务执行时间过长,超过了两个任务的间隔时间,会发生一些缺陷:

import java.util.Timer;
import java.util.TimerTask;

/**
 * \* Created with IntelliJ IDEA.
 * \* User: wugong.jie
 * \* Date: 2018/3/20 13:04
 * \* To change this template use File | Settings | File Templates.
 * \* Description:
 * \
 */
public class TimerDemo {

    private Timer timer;
    public long start;

    public TimerDemo() {
        this.timer = new Timer();
        start = System.currentTimeMillis();
    }

    public void timerOne() {
        timer.schedule(new TimerTask() {
            public void run() {
                System.out.println("timerOne invoked ,the time:" + (System.currentTimeMillis() - start));
//                try {
//                    Thread.sleep(4000);    //线程休眠4s
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
            }
        }, 1000);
    }

    public void timerTwo() {
        timer.schedule(new TimerTask() {
            public void run() {
                System.out.println("timerOne invoked ,the time:" + (System.currentTimeMillis() - start));
            }
        }, 3000);
    }

    public static void main(String[] args) throws Exception {
        TimerDemo test = new TimerDemo();

        test.timerOne();
        test.timerTwo();
    }

}

结果-未sleep:

timerOne invoked ,the time:1000
timerOne invoked ,the time:3001

结果-sleep 4s:

timerOne invoked ,the time:1000
timerOne invoked ,the time:5000

2、Timer抛出异常缺陷

如果TimerTask抛出RuntimeException,Timer会终止所有任务的运行。自己将上述代码的timerOne() 搞出异常,查看timerTwo() 能否执行

对于Timer的缺陷,我们可以考虑 ScheduledThreadPoolExecutor 来替代。Timer是基于绝对时间的,对系统时间比较敏感,而ScheduledThreadPoolExecutor 则是基于相对时间;Timer是内部是单一线程,而ScheduledThreadPoolExecutor内部是个线程池,所以可以支持多个任务并发执行。

TimerTask

131206_fIU8_3027745.png

      TimerTask类是一个抽象类,由Timer 安排为一次执行或重复执行的任务。它有一个抽象方法run()方法,该方法用于执行相应计时器任务要执行的操作。因此每一个具体的任务类都必须继承TimerTask,然后重写run()方法。

      另外它还有两个非抽象的方法:      

boolean cancel():取消此计时器任务。

long scheduledExecutionTime():返回此任务最近实际执行的安排执行时间。

 

转载于:https://my.oschina.net/wugong/blog/1647739

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值