java Timer源码走读

前言 

        之前一直很好奇java的定时器到底是怎么做的?是启动了一个线程,隔一段时间执行一下传入的方法吗(不是)?怎么实现隔一段时间执行一次的逻辑的,是sleep一定时间吗(不是)?

        现在终于知道看一下源码了,没想到逻辑竟然如此简单。。。

例子

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

public class Test {
    public static void main(String[] args) {
        Timer t = new Timer();
        t.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello");
            }
        }, 0, 1000);
        t.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                System.out.println("world!");
            }
        }, 500, 1000);
    }
}

输出:

only once
hello
world!
hello
world!
hello
world!

源码概况

        涉及到四个类:Timer、TimerThread、TimerQueue、TimerTask

        Timer:定时器,里面包含几种schedule方法,可以调度任务

        TimerThread:继承自Thread,是一个线程;Timer里有一个TimerThread的对象,这个对象是真正的定时器执行器(也就是说定时器其实是一个线程,这部分猜想是对的)

        TimerQueue:一个队列,队列里保存的是TimerTask,也就是要执行的任务。队列是用最小堆的方式组织的

        TimerTask:要调度的任务,其实就是实现了Runnable接口的一个抽象类(run方法留给子类实现,所以是抽象类);里边增加了任务调度状态的字段,增加了cancel方法用于定时器使用。

Timer类详述

        最主要的两个变量:TaskQueue queue 和    TimerThread thread;queue用来保存所有要调度的任务,thread是一个单独的线程,对queue里的任务进行调度。

        下面这个threadReaper 也很有意思,是终结方法守卫者,在为了在gc执行的时候做到优雅退出的方法。不详细介绍了,留下参考文章

/**
     * This object causes the timer's task execution thread to exit
     * gracefully when there are no live references to the Timer object and no
     * tasks in the timer queue.  It is used in preference to a finalizer on
     * Timer as such a finalizer would be susceptible to a subclass's
     * finalizer forgetting to call it.
     */
    private final Object threadReaper = new Object() {
        protected void finalize() throws Throwable {
            synchronized(queue) {
                thread.newTasksMayBeScheduled = false;
                queue.notify(); // In case queue is empty.
            }
        }
    };

构造方法

        几个构造方法,其实就两个变量,一个是给thread命名,另一个决定是否给thread设置为后台进程

        然后就启动thread线程。

schedule方法

        schedule,顾名思义,就是调度方法,用来调度任务的。

        这里有三类方法:一类是只调度一次、一类是循环调度(用系统时间)、一类是循环调度(用既定时间)。开头的例子中就分别用了这三种方法。

        后两种比较绕,简单解释一下就是:如果一个任务是循环调度的,当这个任务在某次调度中产生了延迟时,下次调度应该什么时候开始?一种是当前任务调度的时候,用系统时间加上调度间隔就是下次调度时间;另一种就是用任务本次本来该调用的时间+调度间隔,也就是它本来下次该被调度的时间,如果本次调度结束就满足了下次调度时间,那就立马开始下次调度。

        那么,这两种方法是怎么区分的呢?我们可以看到,内部period一个为负一个为正来区分的,也是奇技淫巧了😀

    public void schedule(TimerTask task, Date firstTime, long period) {
        if (period <= 0)
            throw new IllegalArgumentException("Non-positive period.");
        sched(task, firstTime.getTime(), -period);
    }

    public void scheduleAtFixedRate(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);
    }

        我们可以看到,内部其实都是调用的sched方法,逻辑上就是把task加入到队列queue中

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

TimerThread类

        这个类就是真正进行任务定时调度的地方了。有点ThreadPoolExecutor的味道。

        有两个变量:boolean newTasksMayBeScheduled  这个是用来标识定时器是否结束的,Timer的cancel任务里会把它置为false;

                  TaskQueue queue  这是上面Timer里的任务队列,在Timer里传递进来的。

        真正的执行环节:其实就是一个死循环,检查queue队列是否有需要执行的任务。如果queue是空的,就会调用queue的wait方法,这个方法会释放外面的锁,这样timer里就可以往queue里添加任务并唤醒queue上等待的线程(也就是当前这个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
            }
        }
    }
 
    /**
     * The main timer loop.  (See class comment.)
     */
    private void mainLoop() {
        while (true) {
            try {
                TimerTask task;
                boolean taskFired;
                synchronized(queue) {
                    // Wait for queue to become non-empty
                   //如果队列为空并且是有标志位,则等待。没有标志位的情况为不在需要执行timer了,比如cancel或被gc的时候
                    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();//获取就近的task
                    synchronized(task.lock) {
                       //如果该task已经被置为cancelled,则将它从队列里面移出
                        if (task.state == TimerTask.CANCELLED) {
                            queue.removeMin();
                            continue;  // No action required, poll queue again
                        }
                        currentTime = System.currentTimeMillis();
                        executionTime = task.nextExecutionTime;
                        if (taskFired = (executionTime<=currentTime)) {
                            if (task.period == 0) { // period表示该task是一次性的,用完就移出
                                queue.removeMin();//移出task,这块会有queue的重新排序
                                task.state = TimerTask.EXECUTED;//更新状态为执行中
                            } else {
                        //可重复执行的task操作,将重新计算下次执行时间,并重新排序    
                //重点,此处解释为什么period分正负:区别schedule方法和scheduleAtFixedRate
                        //如果是负数,则以当前时间为准,往后计算下次执行时间
                        //如果是正数,则以理论时间为准,往后计算下次执行时间
                                queue.rescheduleMin(
                                  task.period<0 ? currentTime   - task.period
                                                : executionTime + task.period);
                            }
                        }
                    }
                    if (!taskFired) // 如果还没到任务执行时间就处于等待
                        queue.wait(executionTime - currentTime);
                }
                if (taskFired)  // 到执行时间了
                //执行task中的run方法,而不是start方法,所以并不是另起一个线程进行操作
                    task.run();
            } catch(InterruptedException e) {//如果是不能捕获的异常,就会有风险了
            }
        }
    }

TaskQueue类

        用一个数组,以最小堆的形式保存所有待调度的任务。里边的方法都是最小堆的实现形式,不细讲了,有空自己写一些最小堆。

TimerTask类

        要调度的任务,其实就是实现了Runnable接口的一个抽象类(run方法留给子类实现,所以是抽象类);里边增加了任务调度状态的字段,增加了cancel方法用于定时器使用。        

 

参考文章

        代码剖析,写的很好:https://blog.csdn.net/xixi_haha123/article/details/81082321

        终结方法守卫者:https://blog.csdn.net/xl890727/article/details/80082604

 

 

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值