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

    Timer

    1. 使用方法

    public class TimerTest {
        public static void main(String[] args) {
            Timer timer = new Timer(); 
            System.out.format("name:{%s},time:{%s},thread:{%s}
    ", "start", new Date(), Thread.currentThread());
    
            timer.schedule(new TimerTask() {
                @SneakyThrows
                @Override
                public void run() {
                    System.out.format("name:{%s},time:{%s},thread:{%s}
    ", "5s", new Date(), Thread.currentThread());
                    Thread.sleep(5000);
                }
            }, 1000);
            timer.scheduleAtFixedRate(new TimerTask() {
                @SneakyThrows
                @Override
                public void run() {
                    System.out.format("name:{%s},time:{%s},thread:{%s}
    ", "5s2", new Date(), Thread.currentThread());
                    Thread.sleep(5000);
                }
            }, 2000);  
        }
    }
    

    2. 源码解析

    构造方法

        //默认线程名
     public Timer() {
            this("Timer-" + serialNumber());
        }
    
        //是否有守护线程
        public Timer(boolean isDaemon) {
            this("Timer-" + serialNumber(), isDaemon);
        }
     //自定义线程名称
        public Timer(String name) {
            thread.setName(name);
            thread.start();
        }
     //自定义名称和设置是否有守护线程
        public Timer(String name, boolean isDaemon) {
            thread.setName(name);
            thread.setDaemon(isDaemon);
            thread.start();
        }
    

    构造方法中的thread是Timer的内部类, TimerThread extends Thread,构造方法启动线程 , TimerThread持有一个小顶堆队列,TaskQueue

    TimerThread extends Thread
    
     public void run() {
            try {
                //主要逻辑放在mainLoop中
                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) {
                        // 如果队列为空且执行状态正常则等待
                        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
                                    //需要重复
                                    // 由设定任务可知,task.period<0的都是 schedule 方法
                                    // 当是schedule方法时,下次执行时间为当期时间+间隔时间
                                    // 当是scheduleAtFixedRate方法时,下次执行时间为 任务中上次计算出的下次执行时间+间隔时间
                                    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) {
                }
            }
        }
    

    设定任务

    一共有6个方法,

        //延迟一定时间后执行一次
     public void schedule(TimerTask task, long delay) {
            if (delay < 0)
                throw new IllegalArgumentException("Negative delay.");
            sched(task, System.currentTimeMillis()+delay, 0);
        }
    
     //在指定时间执行一次
        public void schedule(TimerTask task, Date time) {
            sched(task, time.getTime(), 0);
        }
    
     //延迟一定时间后执行,并间隔一定时间重复执行
        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);
        }
    
     //在指定时间执行一次,并间隔一定时间重复执行
        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);
        }
    
     //在指定时间执行一次,并间隔一定时间重复执行
        public void scheduleAtFixedRate(TimerTask task, Date firstTime,
                                        long period) {
            if (period <= 0)
                throw new IllegalArgumentException("Non-positive period.");
            sched(task, firstTime.getTime(), period);
        }
    

    schedule与scheduleAtFixedRate主要区别是,如果执行的任务耗时超过了时间间隔,下次的执行时间,schedule是以当前时间+时间间隔计算下次执行时间,scheduleAtFixedRate是以上次计算出的下次执行时间+时间间隔计算下次执行时间

    可以看到其实这6个方法都是调用的 sched(TimerTask task, long time, long 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);
                if (queue.getMin() == task)
                    queue.notify();
            }
        }
    

    举例

    举例说明scheduleAtFixedRate和schedule区别

    设置一个定时任务,每个5秒重复一次,第一次执行耗时6秒,之后每次执行耗时3秒.

    采用schedule方法,则第二次执行时间为第一次执行结束后+5s , 即第11秒执行第二次

    采用scheduleAtFixedRate方法,则第二次执行时间为第一次重复时间+5s,即第10秒执行第二次,

    可以看出schedule方法保证的是前一个任务结束时间和后一个任务开始时间的间隔,scheduleAtFixedRate保证的是任意两个任务开始时间的间隔。

    3. 总结

    • Timer只有一个单线程在轮训任务队列
    • Timer使用synchronized关键字保证线程安全
    • Timer可以在任务里使用线程进行耗时操作,防止耗时太久影响下个任务执行
    • 当有任务抛出RuntimeException异常时,所有任务都会停止,注意执行任务的异常捕获

    本文使用 mdnice 排版

  • 相关阅读:
    hashCode花式卖萌
    2017年的小总结
    多线程环境下的单例模式
    Servlet过滤器简单探索
    最长回文子序列(LPS)
    最短编辑距离问题
    赫夫曼编码
    DNA序列对齐问题
    同时寻找序列的最大最小值
    最长公共子序列(LCS)
  • 原文地址:https://www.cnblogs.com/A-yes/p/13404201.html
Copyright © 2011-2022 走看看