美文网首页
学习 ScheduledExecutorService

学习 ScheduledExecutorService

作者: 程序员网址导航 | 来源:发表于2019-10-27 18:11 被阅读0次

ScheduledExecutorService是什么?

ScheduledExecutorService是Java线程池中重要的几个接口之一。它除了支持原生线程池的功能外,同时支持定时任务处理的能力。在很多优秀的框架、中间件中都有被使用。

在JDK中,为ScheduledExecutorService提供了一个默认的实现类:ScheduledThreadPoolExecutor,类图:

image.png

ScheduledExecutorService的使用

image.png

ScheduledExecutorService 包含三个方法:

schedule() :返回一个ScheduledFuture接口的具体实现。

ScheduledFuture是一个延迟的、结果可接受的操作,可将其取消。通常已安排的 future 是用 ScheduledExecutorService 安排任务的结果。

ScheduledFuture只是在Future基础上还集成了Comparable和Delayed的接口。使其具有延迟、排序、获得异步计算结果的特性。

它用于表示ScheduledExecutorService中提交了任务的返回结果。我们通过Delayed的接口getDelay()方法知道该任务还有多久才会被执行。

JDK中并没提供ScheduledFuture的实现类。只有在ScheduledExecutorService中提交了任务,才能返回一个实现了ScheduledFuture接口的对象。

scheduleAtFixedRate() :此方法用于周期性执行任务; 当任务耗时长于周期,那么下一个周期任务将在上一个执行完毕之后马上执行; 当任务耗时短于周期,那么正常周期性执行。

scheduleWithFixedDelay():此方法用于周期性执行; 无论上一个方法耗时多长,下一个方法都会等到上一个方法执行完毕之后,再经过delay的时间才执行。

下面我以示例的方式验证一下上述的解释是否合理。

schedule()

/**
 * @author 王琦 <QQ.Email>1124602935@qq.com</QQ.Email>
 * @date 19/10/27 下午3:42
 * @description
 */
public class ScheduledServiceTest {

    private static final int delay_seconds = 3;
    private static final TimeUnit time_unit = TimeUnit.SECONDS;

    private static ScheduledExecutorService scheduler;

    public ScheduledServiceTest(int nThreads){
        scheduler = Executors.newScheduledThreadPool(nThreads, Executors.defaultThreadFactory());
    }

    public void scheduleTest() throws ExecutionException, InterruptedException {

        for (int i = 1; i <= 5; i++) {

            int tmp = i;
            ScheduledFuture<String> scheduledFuture = scheduler.schedule(new Callable<String>() {
                @Override
                public String call() throws Exception {
                    return "scheduleTest"+ tmp;
                }

            }, delay_seconds, time_unit);

            // 通过ScheduledFuture的getDelay方法获取多久后执行
            System.out.println("任务"+tmp+"将在 "+scheduledFuture.getDelay(time_unit)+" 秒后执行");
            // 通过ScheduledFuture的get方法获取执行返回的结果
            System.out.println("任务"+tmp+"执行结果: "+scheduledFuture.get()+"\n");
        }

        scheduler.shutdown();
        System.out.println("scheduler 终止!!");
    }


    public static void main(String[] args) throws Exception {
        ScheduledServiceTest scheduledService = new ScheduledServiceTest(10);
        scheduledService.scheduleTest();
    }
}

输出结果:

任务1将在 2 秒后执行
任务1执行结果: scheduleTest1

任务2将在 2 秒后执行
任务2执行结果: scheduleTest2

任务3将在 2 秒后执行
任务3执行结果: scheduleTest3

任务4将在 2 秒后执行
任务4执行结果: scheduleTest4

任务5将在 2 秒后执行
任务5执行结果: scheduleTest5

scheduler 终止!!

scheduleAtFixedRate()

public class ScheduledServiceTest {

    private static final int delay_seconds = 10;
    private static final TimeUnit time_unit = TimeUnit.SECONDS;

    private static ScheduledExecutorService scheduler;

    public ScheduledServiceTest(int nThreads){
        scheduler = Executors.newScheduledThreadPool(nThreads, Executors.defaultThreadFactory());
    }

    public void scheduleAtFixedRateTest() throws Exception {

        Random r = new Random();
        SimpleDateFormat format = new SimpleDateFormat("YYYY-MM-dd HH:mm:ss");

        System.out.println("当前时间:"+format.format(System.currentTimeMillis()));

        scheduler.scheduleAtFixedRate(new Runnable() {

            @Override
            public void run() {

                String logStart = format.format(System.currentTimeMillis());
                System.out.print("> "+logStart);

                // 生成10以内的一个随机数,模拟当前任务的执行时间 mockRunTime 秒
                int mockRunTime = r.nextInt(20);
                try {
                    Thread.sleep(mockRunTime * 1000);
                } catch (InterruptedException e) { }

                String logEnd = format.format(System.currentTimeMillis());
                String compare = "执行用时" + (mockRunTime >= delay_seconds ? " > 周期时间" : " < 周期时间");
                System.out.println(",  任务执行完毕!用时:"+mockRunTime+"秒,当前"+logEnd+",【"+compare+"】");
            }
        }, 0, delay_seconds, time_unit);
    }


    public static void main(String[] args) throws Exception {
        ScheduledServiceTest scheduledService = new ScheduledServiceTest(100);
        scheduledService.scheduleAtFixedRateTest();
    }
}

执行结果:

正常预期08秒 > 2019-10-27 17:36:08,  任务执行完毕!用时:10秒,当前2019-10-27 17:36:18,【执行用时 > 周期时间】
正常预期18秒 > 2019-10-27 17:36:18,  任务执行完毕!用时:13秒,当前2019-10-27 17:36:31,【执行用时 > 周期时间】
正常预期28秒 > 2019-10-27 17:36:31,  任务执行完毕!用时:2秒,当前2019-10-27 17:36:33,【执行用时 < 周期时间】
正常预期38秒 > 2019-10-27 17:36:38,  任务执行完毕!用时:12秒,当前2019-10-27 17:36:50,【执行用时 > 周期时间】
正常预期48秒 > 2019-10-27 17:36:50,  任务执行完毕!用时:6秒,当前2019-10-27 17:36:56,【执行用时 < 周期时间】
正常预期58秒 > 2019-10-27 17:36:58,  任务执行完毕!用时:2秒,当前2019-10-27 17:37:00,【执行用时 < 周期时间】

scheduleWithFixedDelay()

/**
 * @author 王琦 <QQ.Email>1124602935@qq.com</QQ.Email>
 * @date 19/10/27 下午3:42
 * @description
 * 不管当前任务耗时多久,下个任务必须在当前任务之后完毕后在加上周期时间,才可以开始执行
 */
public class ScheduledServiceTest {

    private static final int delay_seconds = 10;
    private static final TimeUnit time_unit = TimeUnit.SECONDS;

    private static ScheduledExecutorService scheduler;

    public ScheduledServiceTest(int nThreads){
        scheduler = Executors.newScheduledThreadPool(nThreads, Executors.defaultThreadFactory());
    }

    public void scheduleAtFixedRateTest() throws Exception {

        Random r = new Random();
        SimpleDateFormat format = new SimpleDateFormat("YYYY-MM-dd HH:mm:ss");

        System.out.println("当前时间:"+format.format(System.currentTimeMillis()));

        scheduler.scheduleWithFixedDelay(new Runnable() {

            @Override
            public void run() {

                String logStart = format.format(System.currentTimeMillis());
                System.out.print("> "+logStart);

                // 生成10以内的一个随机数,模拟当前任务的执行时间 mockRunTime 秒
                int mockRunTime = r.nextInt(20);
                try {
                    Thread.sleep(mockRunTime * 1000);
                } catch (InterruptedException e) { }

                String logEnd = format.format(System.currentTimeMillis());
                String compare = "执行用时" + (mockRunTime >= delay_seconds ? " > 周期时间" : " < 周期时间");
                System.out.println(",  任务执行完毕!用时:"+mockRunTime+"秒,当前"+logEnd+",【"+compare+"】");
            }
        }, 0, delay_seconds, time_unit);
    }


    public static void main(String[] args) throws Exception {
        ScheduledServiceTest scheduledService = new ScheduledServiceTest(100);
        scheduledService.scheduleAtFixedRateTest();
    }
}

执行结果:

当前时间:2019-10-27 17:40:32
> 2019-10-27 17:40:32,  任务执行完毕!用时:10秒,当前2019-10-27 17:40:42,【执行用时 > 周期时间】
> 2019-10-27 17:40:52,  任务执行完毕!用时:3秒,当前2019-10-27 17:40:55,【执行用时 < 周期时间】
> 2019-10-27 17:41:05,  任务执行完毕!用时:4秒,当前2019-10-27 17:41:09,【执行用时 < 周期时间】
> 2019-10-27 17:41:19,  任务执行完毕!用时:12秒,当前2019-10-27 17:41:31,【执行用时 > 周期时间】
> 2019-10-27 17:41:41,  任务执行完毕!用时:15秒,当前2019-10-27 17:41:56,【执行用时 > 周期时间】
> 2019-10-27 17:42:06,  任务执行完毕!用时:16秒,当前2019-10-27 17:42:22,【执行用时 > 周期时间】

看三个执行结果跟上面的解释也是吻合的,我简单分别分析一下三个方法的场景:

schedule(): 适用于多线程环境下”需要获取任务返回结果“ 的定时任务

scheduleAtFixedRate():适用于多线程环境下 ”更关注任务具体执行耗时、间隔“

scheduleWithFixedDelay:适用于多线程环境下”不关心每次任务的具体执行耗时,只关心固定间隔“

关于ScheduledExecutorService的使用主要就是这三个方法的使用,重要的是根据我们的需要选择合适。 实现原理可以围绕其线程池(构造器中直接调用父类线程池的构造器,到太多东西)和定时器实现两部分学习源码了解。

画了两张图,对应到源码学习思路会更清晰一些:

(1)schedule方法的实现原理示意图:

image.png

(2)scheduleAtFixedRate()和scheduleWithFixedDelay()实现原理:

image.png

图中提到的队列选用优先队列(小顶堆)而不是有序的数组或者链表?因为优先队列只需要确保局部有序,它的插入、删除操作的复杂度都是O(log n);而有序数组的插入和删除复杂度为O(n);链表的插入复杂度为O(n),删除复杂度为O(1)。即优先队列性能最好。

原文:初识 ScheduledExecutorService

相关文章

网友评论

      本文标题:学习 ScheduledExecutorService

      本文链接:https://www.haomeiwen.com/subject/lqrzmctx.html