Timer源码分析

java.util.Timer简介

        Timer是用于管理在后台执行的延迟任务或周期性任务,其中的任务使用java.util.TimerTask表示。任务的执行方式有两种:

按固定速率执行:即scheduleAtFixedRate的两个重载方法
按固定延迟执行:即schedule的4个重载方法

        我们通过源码来分析它的特性

        首先,看一个例子:

public static void main(String[] args) throws Exception {

        Timer timer = new Timer();
        Thread.sleep(10000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("haha");
                try {
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, 1000, 1000);

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("heihei");
            }
        }, 1000,1000);
    }

这个例子中,我们在主线程中实例化了一个Timer对象,然后在该对象中添加了两个任务。

那么这些任务是怎么执行的?有什么样的一些特性?我们通过源码一步步分析,来梳理该组件的一些特点。

我们从Timer初始化开始分析:

Timer内部有两个属性

private final TaskQueue queue = new TaskQueue();

private final TimerThread thread = new TimerThread(queue);

有一个属性queue,是用来存储任务的,我们上面的例子中,添加了两个任务,其实就是new 了两个TimeTask对象,放在了这个queue中。

        另一个属性是thread,在初始化的时候就会启动该线程:

public Timer(String name) {
        thread.setName(name);
        thread.start();
    }

 也就是说在在实例化Timer的同时,我们系统中就生成了一个新的线程,接下来看下该线程的run方法。该TimerThread的run方法中就是一个mianLoop方法:

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

下面逐行解释一下mainLoop的代码含义 

private void mainLoop() {
        while (true) {
            try {
                TimerTask task;
                boolean taskFired;
                synchronized(queue) {
                    // Wait for queue to become non-empty
                    //如果queue是空的,会一直等待下去,直到Timer添加任务时notify后被唤醒
                    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中移除
                            queue.removeMin();
                            continue;  // No action required, poll queue again
                        }
                        currentTime = System.currentTimeMillis();
                        executionTime = task.nextExecutionTime;
           //如果下次执行的时间早于当前时间,则说明达到了执行的时间点,即taskFired为true
                        if (taskFired = (executionTime<=currentTime)) {
                            if (task.period == 0) { // Non-repeating, remove
                            //如果period是0,代表该任务不是一个重复执行的任务,可以从queue中移除
                                queue.removeMin();
                                task.state = TimerTask.EXECUTED;
                            } else { // Repeating task, reschedule
          //如果是重复任务,则修改下次执行的时间:如果period为正数,代表以固定的频率执行任务,如果是负数,代表以固定的延迟时间执行任务
                                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) {
            }
        }
    }

   这个方法整个就是一个大的死循环。
        总结这些对象及属性引用关系如下,Timer对象中有queue,添加任务的时候其实就是在queue中添加一个TimeTask对象,而Timer对象和TimerThread引用的是同一个queue,而ThimeThread的mainLoop方法中的逻辑的不断循环判断queue中各个TimeTask对象的状态,进而进行相应的逻辑处理



上面是实例化Timer的源码分析,总结就是:在实例化的同时,产生了一个新的线程,这个线程在一个大循环中,在不断的轮询queue,如果这个queue是空的,改线程就会调wait方法等待。

        下面是添加任务的方法,在添加任务的时候,会判断queue之前是不是为空,如果为空,就说明执行任务的线程在wait,需要进行唤醒。

public void schedule(TimerTask task, long delay, long period) {
        if (delay < 0)
            throw new IllegalArgumentException("Negative delay.");
        if (period <= 0)
            throw new IllegalArgumentException("Non-positive period.");
        sched(task, System.currentTimeMillis()+delay, -period);
    }
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);
//这里结合TimerThread的mainLoop方法看,如果当前加入的task是queue中最小的那个,说明之前的queue是空的,TimerThread在wait,需要被唤醒
            if (queue.getMin() == task)
                queue.notify();
        }
    }
void rescheduleMin(long newTime) {
        queue[1].nextExecutionTime = newTime;
        fixDown(1);
    }

重复任务需要重新计算下次执行的时间,然后通过fixDown对任务进行重排,将其顺序与后面的任务进行对比下。这种方式并非排序,而是找到一个合适的位置来交换,因为并不是通过队列逐个找的,而是每次移动一个二进制为,例如传入1的时候,接下来就是2、4、8、16这些位置,找到合适的位置放下即可,顺序未必是完全有序的,它只需要看到距离调度部分的越近的是有序性越强的时候就可以了,这样即可以保证一定的顺序性,达到较好的性能。
 

通过以上源码,尤其是大循环内的逻辑解释,可以得出如下结论:

1、由于执行任务的线程只有一个,所以如果某个任务的执行时间过长,那么将破坏其他任务的定时精确性。如一个任务每1秒执行一次,而另一个任务执行一次需要5秒,那么如果是固定速率的任务,那么会在5秒这个任务执行完成后连续执行5次,而固定延迟的任务将丢失4次执行。

2、如果执行某个任务过程中抛出了异常,那么执行线程将会终止,导致Timer中的其他任务也不能再执行。

3、Timer使用的是绝对时间,即是某个时间点,所以它执行依赖系统的时间,如果系统时间修改了的话,将导致任务可能不会被执行。

更好的替代方法

        由于Timer存在上面说的这些缺陷,在JDK1.5中,我们可以使用ScheduledThreadPoolExecutor来代替它,使用Executors.newScheduledThreadPool工厂方法或使用ScheduledThreadPoolExecutor的构造函数来创建定时任务,它是基于线程池的实现,不会存在Timer存在的上述问题,当线程数量为1时,它相当于Timer。

        多线程并行处理定时任务时,Timer运行多个TimeTask时,只要其中之一没有捕获抛出的异常,其它任务便会自动终止运行,使用ScheduledExecutorService则没有这个问题。

        由于执行任务的线程只有一个,所以如果某个任务的执行时间过长,那么将破坏其他任务的定时精确性。

彩蛋:

        虽然Timer有很多缺点,但是在分析代码的过程中,发现有一些编码的思想值得学习。

1. 在做循环逻辑的时候,可以更多的使用wait和notify做多线程的交互,减少无用的CPU消耗

2. 在移除某个任务的时候,把列表最后一个任务覆盖到待删除任务的位置,然后最后一个引用被置为null,极大减小了操作的时间复杂度。

void removeMin() {
    queue[1] = queue[size];
    queue[size--] = null;  // Drop extra reference to prevent memory leak
    fixDown(1);
}

3. TaskQueue实现了优先队列的数据结构,内部是一个数组,数组内容实际上是从下标1开始填充的;它其实是用balanced binary heap来表示的,设父节点是queue[n],则它的两个字节点分别是queue[2*n]queue[2*n+1]。在合适的场景使用合适的数据结构,可以更小成本的实现场景需求。

        

参考:

https://www.cnblogs.com/heqiyoujing/p/10416065.html

https://blog.csdn.net/yaomingyang/article/details/82113216

https://blog.csdn.net/xieyuooo/article/details/8607220

https://blog.csdn.net/lfsf802/article/details/41621731

https://blog.csdn.net/xieyuooo/article/details/8607220/

https://segmentfault.com/a/1190000009246096

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值