zoukankan      html  css  js  c++  java
  • 【定时任务】Timer

    Java原生api Timer类就可以实现简单的定时任务。下面将简单介绍一下Timer。

    一、使用 Timer 实现定时任务

    具体代码如下。

    可以看到我们主要是分三步进行的

    1、new Timer() 创建定时器

    2、new TimerTask() 创建任务。这里是通过继承TimerTask类实现的。

    3、使用定时器调度。这里我们使用的是Timer类中的schedule(TimerTask task, long delay, long period) 方法,里面有三个参数,

       第一个参数表示任务,第二个参数表示延迟的时间,第三个参数表示任务执行的间隔(也就是每隔多少秒执行一次任务)。

    public class TimerTest {
        
        public static void main(String[] args) {
            Timer timer = new Timer();
            timer.schedule(new TimerTest().new MyTask(), 1000, 5000);
        }
        
        /**
         * 任务
         * @author LKB
         *
         */
        class MyTask extends TimerTask{
    
            @Override
            public void run() {
                // TODO Auto-generated method stub
                System.out.println("current time is " + new Date());
            }        
        }
    }

    二、Timer 实现定时任务的原理

    Timer 的实现需要四个类:Timer TimerThread TaskQueue TimerTask。

    2.1 TimerTask

    其中 TimerTask 表示任务,它是个抽象类TimeTask 抽象类  继承Runnable。可以看到每个Task都是一个runnable对象。

    TimeTask中全局变量有

       lock    锁
        state    VIRGIN: 任务未被调度
                SCHEDULED: 任务被调度但是还未执行
                EXECUTED: 任务已经被执行但是未被取消
                CANCELLED:任务被取消
                nextExecutionTime:任务下一次被执行的时间
                period:重复任务之间的间隔

    内部方法有

      //取消调度
        public boolean cancel() {
            synchronized(lock) {
                boolean result = (state == SCHEDULED);
                state = CANCELLED;
                return result;
            }
        }
        //计算下次执行时间
        public long scheduledExecutionTime() {
            synchronized(lock) {
                return (period < 0 ? nextExecutionTime + period
                                   : nextExecutionTime - period);
            }
        }

    2.2 TaskQueue

    TaskQueue 是一个优先队列,它是通过一个数组实现的。

    全局变量有:

      private TimerTask[] queue = new TimerTask[128];  一个数组,数组元素类型为TimerTask。这个数组用来实现优先队列。
        
        private int size = 0; //优先队列中的任务个数  需要注意的是任务在优先队列中的存储是queue[1]-queue[size]

    内部的方法有挺多的,最基本的我认为是下面几个:

      /**
         * 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);
        }
    
        /**
         * Return the "head task" of the priority queue.  (The head task is an
         * task with the lowest nextExecutionTime.)
         */
        TimerTask getMin() {
            return queue[1];
        }
        
        /**
         * Remove the head task from the priority queue.
         */
        void removeMin() {
            queue[1] = queue[size];
            queue[size--] = null;  // Drop extra reference to prevent memory leak
            fixDown(1);
        }

    add 是往队列中添加任务,removeMin 是移除队头元素。因为 TaskQueue 是通过优先队列实现的,所以的删除或者添加一个元素的复杂度为O(lgN)。

    2.3 TimerThread

    TimerThread 继承Thread 类,Timer 定时器正是通过这个线程实现任务调度的。

    全局变量有

    boolean newTasksMayBeScheduled = true;  //如果该标志位false 则表示对 Timer 对象没有任何有效的引用,没有任何的任务需要做了,线程可以优雅地终止了
    private TaskQueue queue; //任务队列

    重写的 run  方法如下

        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
                        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)) {
                                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) {
                }
            }
        }

    可以看到TimerThread 运行的方式很简单,在一个while(true)循环中,首先判断任务队列中是否有任务,没有任务则一直等待;

    如果有任务,取出该任务(运行时间离当前时间最近的任务),判断任务时间,任务时间到了,则调用task.run()运行任务。

    2.4 Timer

    Timer 是一个调度器,该调度器内部只有一个调度线程,并且只有一个任务队列。

    全局变量有:

       private final TaskQueue queue = new TaskQueue(); //任务队列
       private final TimerThread thread = new TimerThread(queue); //调度线程
       private final Object threadReaper = new Object() {
           protected void finalize() throws Throwable {
               synchronized(queue) {
                   thread.newTasksMayBeScheduled = false;
                   queue.notify(); // In case queue is empty.
               }
           }
       }; //该对象可以使得当没有任何有效引用指向Timer对象时,定时器被优雅回收
       
        /**
         * This ID is used to generate thread names.
         */
        private final static AtomicInteger nextSerialNumber = new AtomicInteger(0);
        private static int serialNumber() {
            return nextSerialNumber.getAndIncrement();
        }

    最基本的构造器如下。可以看到,当我们new一个 Timer 对象出来时,Timer 中对应的调度线程就被启动了。

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

    最基本的调度方法如下。可以看出它其实就是对任务队列的一个操作。

    首先将当前任务配置好(计算好下次执行时间,间隔与状态),然后加入任务队列,如果刚好是队列的队头,再将队列唤醒,让队列直接处理。

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

    综上,我们可以任务定时器的工作原理如下:

    Timer 内部拥有一个 TimerThread 和 一个TaskQueue,当我们new Timer() 时,我们就拥有了这两个对象,并启动了TimerThread。

    当我们这样调动  timer.schedule(new TimerTest().new MyTask(), 1000, 5000); schedule方法时我们就将对应的任务加到TaskQueue中。

    TimeThread 负责调度TaskQueue,TaskQueue 是一个优先队列,里面的元素全是TimerTask 对象。

  • 相关阅读:
    fescar源码解析系列(一)之启动详解
    dubbo源码解析二 invoker链
    dubbo源码解析一
    CSP-S 2021 游记
    使用SpEL记录操作日志的详细信息
    Router 重定向和别名是什么?
    vue项目做seo(prerender-spa-plugin预渲染)
    vue3.0初体验有哪些实用新功能
    uniapp弹窗踩坑
    Spring boot application.properties 配置
  • 原文地址:https://www.cnblogs.com/cuglkb/p/7801649.html
Copyright © 2011-2022 走看看