聊聊定时器的"追赶性"

261 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第9天,点击查看活动详情

首先问大家一个问题,在此之前你是否听说过定时器的"追赶性",你是否研究过Timer类对定时任务进行预设时常用到的两个方法:

public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period)
public void schedule(TimerTask task, Date firstTime, long period)

有什么不同么,今天笔者带领大家深度了解一下这两个方法本质上的区别.

1.schedule方法详解

  • 当schedule设置的预期第一次执行时间在当前系统时间之前,定时器启动后立即进行第一次执行.
  • 当schedule设置的预期第一次执行时间大于当前系统时间时,定时器启动后等待预设的第一次执行时间到来后进行第一次执行.

上面两种情况,Timer会以实际第一次执行的时间为基准,后续按如下策略进行执行:

策略一:"连轴转型"

当任务执行耗时大于 schedule方法中设置的period时,任务执行完毕后直接进入下一轮任务的执行当中,此时period无效; 策略二:"按部就班型"

当任务执行耗时小于schedule方法中设置的period时,任务按照第一次执行时的开始时间为基准,period固定时间间隔周期执行.

测试:
 public class SchedualTest {
    static class Task extends TimerTask {
        public void run() {
            try {
                System.out.println("begin of timer = " + new Date());
//                Thread.sleep(1000);//执行耗时
                System.out.println("end of timer = " + new Date());
                System.out.println("-------------------------------------");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
 
 
    public static void main(String[] args) {
        Task task = new Task();
        System.out.println("current time = " + new Date());
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.SECOND, calendar.get(Calendar.SECOND) + 20);
        Date date = calendar.getTime();
        System.out.println("plan to do time = " + date);
        new Timer().schedule(task, date, 2000);
 
    }
}

2. scheduleAtFixedRate详解

关于scheduleAtFixedRate的执行策略相比schedule要复杂的多,现分别说明:

2.1 当scheduleAtFixedRate设置的预期第一次执行时间小于当前系统时间时,定时器启动后立即进行第一次执行,并以此为基准.

实际场景:

  • A.趋于好转(任务执行耗时小于period),先连续补充执行N次,N=(第一次执行开始时间-预期第一次执行时间)/period,直到补充执行结束后,恢复正常节奏,即以最后一次补充执行的开始执行时间为基准,period固定时间间隔周期执行.通俗解释,按计划本来上午应该干完的工作,结果家里有事调休了下午才来公司上班,上班后急急忙忙把上午的工作赶出来,直到干出来后,工作节奏开始恢复正常,接着处理下午的工作,这种类似现实工作场景的处理方式,称作"追赶性"
  • B.趋于恶化,(任务执行耗时大于period)此时上一次任务执行完毕后直接进入下一轮的任务执行当中进行任务补充执行,永远赶不上,永远还不完.此时period无效,

2.2 当scheduleAtFixedRate设置的预期第一次执行时间大于当前系统时间时,定时器启动后等待预设的第一次执行时间到来后进行第一次执行,并以此为基准.

实际场景:

  • A.按部就班型(任务执行耗时小于period), 第一次执行完,并以此为基准,period固定时间间隔周期执行
  • B.连轴转型(任务执行耗时大于period),此时上一次任务执行完毕后直接进入下一轮的任务执行当中.

总结一下,scheduleAtFixedRate这个方法顾名思义,以"相对固定"的间隔进行执行,注意是相对固定,定时器保证的是在一个相当长的时间里平均下来是每period执行一次,至于其中某几次的执行是否每period执行一次并不保证,定时器内部会通过追赶性来达到(执行总次数/时间总长度=1次/period)成立,即长远来讲以一个相对固定的频度执行.

测试:
public class SchedualAtFixedRateTest {
 
    static class Task extends TimerTask {
        public void run() {
            try {
                System.out.println("-------------------------------------");
                System.out.println("begin of timer = " + new Date());
                Thread.sleep(3000);//执行耗时
                System.out.println("end of timer = " + new Date());
                System.out.println("-------------------------------------");
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }
 
    public static void main(String[] args) {
        Task task = new Task();
        System.out.println("current time = " + new Date());
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.SECOND, calendar.get(Calendar.SECOND) + 20);
        Date date = calendar.getTime();
        System.out.println("plan to do time = " + date);
        new Timer().scheduleAtFixedRate(task,date,2000);
    }=