详解:被人误解的ScheduledThreadPoolExecutor定时器

总所周知,ScheduledThreadPoolExecutor是更优于Timer的JDK定时任务,该类支持多线程执行定时任务,能够保证更加准确的时间间隔。该类有三个核心方法,他们分别是:

  • schedule 创建并执行在给定延迟后启用的单次操作。
    public ScheduledFuture<?> schedule(Runnable command,
    long delay,
    TimeUnit unit)
  • scheduleAtFixedRate 创建并执行在给定的初始延迟之后,随后以给定的时间段首先启用的周期性动作
    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
    long initialDelay,
    long period,
    TimeUnit unit)
  • scheduleWithFixedDelay 创建并执行在给定的初始延迟之后首先启用的定期动作,随后在一个执行的终止和下一个执行的开始之间给定的延迟
    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
    long initialDelay,
    long delay,
    TimeUnit unit)

其中schedule方法和scheduleWithFixedDelay完全可以单线程实现,执行效率什么的不必说了,如果非要了解一下,请参考我的上一篇文章:JDK中的定时器
在上一篇文章中,我们做了一个实验,如果用一秒的时间间隔执行需要花费两秒的任务,那么会发生什么?
实验结果是Timer的scheduleAtFixedRate任务间隔大约是两秒,而ScheduledThreadPoolExecutor的scheduleWithFixedDelay时间间隔大约是三秒。
1、因为Timer是单线程,它需要等待前一个任务执行完成才能执行下一个任务,就算计时间隔早就计时结束也不会立刻执行下一个任务,因为它是一个单线程的任务。
2、因为ScheduledThreadPoolExecutor的scheduleWithFixedDelay的设定是等到任务执行结束才会计时,等待计时结束才执行下一次任务。所以scheduleWithFixedDelay的两次任务间隔实际上是任务所花时间+任务时间间隔,用时是三秒是合理的

但是问题是传说中拥有多线程的ScheduledThreadPoolExecutor的scheduleAtFixedRate会是怎样的结果呢?按照网上普遍的多线程的吹嘘,它会不会等到任务间隔时间一到,就立马开启另一个进程来启动下一个任务,从而完美保证任务间隔呢?
其实不然,它的实验结果和Timer基本一致:

 @Test
    public void executorTimer2() throws IOException
    {
    
    
        // TODO code application logic here
        //构造一个ScheduledThreadPoolExecutor对象,并且设置它的容量为5个
        stpe = new ScheduledThreadPoolExecutor(5);
        //隔2秒后开始执行任务,并且在上一次任务开始后隔1秒再执行一次;
        stpe.scheduleAtFixedRate(new Runnable()
        {
    
    
            @Override
            public void run()
            {
    
    
                index++;
                System.out.println("executor= " + getTimes()+" "  +index);
                try {
    
    
                    //模拟任务执行需要2秒的时间
                    Thread.currentThread().sleep(2000);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                if(index >=10){
    
    
                    stpe.shutdown();
                    if(stpe.isShutdown()){
    
    
                        System.out.println("停止了????");
                    }
                }
            }
        }, 2, 1, TimeUnit.SECONDS);
        System.in.read();
    }

执行结果:

executor= 2020-12-29 22:09:12 Tue 1
executor= 2020-12-29 22:09:14 Tue 2
executor= 2020-12-29 22:09:16 Tue 3
executor= 2020-12-29 22:09:18 Tue 4
executor= 2020-12-29 22:09:20 Tue 5
executor= 2020-12-29 22:09:22 Tue 6
executor= 2020-12-29 22:09:24 Tue 7
executor= 2020-12-29 22:09:26 Tue 8
executor= 2020-12-29 22:09:28 Tue 9
executor= 2020-12-29 22:09:30 Tue 10
停止了????

可以看到,它和timer的scheduleAtFixedRate一样,虽然设置了一秒的时间间隔,但是却实际上间隔两秒。是不是瞬间感觉被骗了?其实又不然!这是以前中文文档的翻译问题,人家压根就没说当任务时长大于任务间隔的时候会多线程执行任务,且看官方解释:

创建并执行在给定的初始延迟之后,随后以给定的时间段首先启用的周期性动作; 那就是执行将在initialDelay之后开始,然后initialDelay+period ,然后是initialDelay + 2 * period ,等等。 如果任务的执行遇到异常,则后续的执行被抑制。 否则,任务将仅通过取消或终止执行人终止。 如果任务执行时间比其周期长,则后续执行可能会迟到,但不会同时执行。

傻了吧,如果任务执行时间比其周期长,则后续执行可能会迟到,但不会同时执行。
相信这时候拍案而起,准备弃用ScheduledThreadPoolExecutor,转而使用Timer,劝君三思,根据我的实验,当任务执行时间吗,没有其周期长的时候,ScheduledThreadPoolExecutor的间隔比Timer准确多了。
所以,还是使用ScheduledThreadPoolExecutor吧!

可能有的同学会说,这样的定时器太古老了,都不是设定每天定点触发……
胡说,我来给你们写一个:

 /**
     * 每天晚上8点执行一次
     * 每天定时安排任务进行执行
     */
    @Test
    public  void executeEightAtNightPerDay()
    {
    
    
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(3);
        long oneDay = 24 * 60 * 60 * 1000;
        long initDelay = getTimeMillis("20:00:00") - System.currentTimeMillis();
        initDelay = initDelay > 0 ? initDelay : oneDay + initDelay;

        executor.scheduleAtFixedRate(
                new Runnable()
                {
    
    
                    @Override
                    public void run()
                    {
    
    
                        try {
    
    
                            Thread.sleep(50);
                        } catch (InterruptedException e) {
    
    
                            e.printStackTrace();
                        }
                        System.out.println("This is a echo server. The current time is " +
                                System.currentTimeMillis() + ".");
                    }
                },
                initDelay,
                oneDay,
                TimeUnit.MILLISECONDS);
    }

/**
 * 获取指定时间对应的毫秒数
 * @param time "HH:mm:ss"
 * @return
 */
        private static long getTimeMillis(String time)
        {
    
    
            try
            {
    
    
                DateFormat dateFormat = new SimpleDateFormat("yy-MM-dd HH:mm:ss");
                DateFormat dayFormat = new SimpleDateFormat("yy-MM-dd");
                Date curDate = dateFormat.parse(dayFormat.format(new Date()) + " " + time);
                return curDate.getTime();
            } catch (ParseException e)
            {
    
    
                e.printStackTrace();
            }
            return 0;
        }

没有困难的工作,只有坚强的打工人!

猜你喜欢

转载自blog.csdn.net/weixin_41674401/article/details/111937964