从简单到复杂学习任务调度(1)

2023-03-02 19:06:28 浏览数 (3)

前言

今天来分享一下任务调度,任务调度在我们项目中是不可避免的,只是不同的场景,不同的业务复杂程度和业务要求,我们会使用不同的任务调度实现,而任务调度的实现方式以及框架有很多,在Java语言层面,可以使用Timer类来实现,也可以使用定时线程池ScheduledExecutorService来实现,如果使用Spring框架,可以使用注解@Scheduled配合CRON表达式来实现任务调度,现成的框架我们可以使用Quartz,xxl-job,Elastic-Job,PowerJob等。

此系列文章我们主要学习xxl-job,它比较轻量级,学习门槛更低,使用成本更低,对于中小型企业来说是完全能满足的,那么在说xxl-job之前,我们会先演示Timer,ScheduledExecutorService,Spring提供的任务调度,以便我们更加了解任务调度,说完这些简单的任务调度类和注解,我们再去学习xxl-job,学习xxl-job的使用,以及架构,源码。

Java Timer类

Timer是Java util包下的一个任务调度工具类,Timer类主要包括两个重要的部门,TimerTask,TaskQueue,TimerThread。

TimerTask

TimerTask就是一个任务,我们要使用Timer,我们的定义任务就需要继承TimerTask这个抽象类,TimerTask实现Runnable接口,可见我们的任务是需要使用线程去调度的。

TaskQueue

TaskQueue顾名思义就是任务队列,我们的任务就是保存在TaskQueue中,然后等待调度,TaskQueue是一个最小堆,根据执行时间进行排序,所以堆顶始终都是最先执行的任务。

TimerThread

TimerThread是继承自Thread,它的作用是开启一个后台线程不停地去TaskQueue中获取任务,然后执行任务。

Timer简单示例

自定义一个任务DemoTask

Timer实现的定时任务比较简单,schedule是执行一次任务,scheduleAtFixedRate是在固定的时间间隔执行任务,比如1000ms执行一次,Timer还有几个方法,就不展开去说,有兴趣可以自行去研究。

代码语言:javascript复制
public class TimerTest {
    public static void main(String[] args) {
        Timer timer = new Timer();
        DemoTask demoTask = new DemoTask();
        //固定时间间隔反复执行任务
        timer.scheduleAtFixedRate(demoTask, 3000, 1000);
        //执行一次
        timer.schedule(demoTask, 2000);
    }

    private static class DemoTask extends TimerTask{
        @Override
        public void run() {
            System.out.println("执行任务");
        }
    }
}

ScheduledExecutorService

ScheduledExecutorService是java并发包concurrent下的一个调度线程池,它实现ExecutorService接口,所以可以看出它是Java线程池的一种,我们可以使用Executors创建一个ScheduledExecutorService,也可以自定义手动创建一个,ScheduledExecutorService提供了比较一些定时任务的方法供我们使用,相对于Timer,ScheduledExecutorService能够获取任务的执行结果,因为它的返回值是ScheduledFutureScheduledFuture继承Future,而Future可以获取一步异步执行的结果,反观Timer,它的任务必须继承TimerTask,而TimerTask实现Runnable接口,其run()方法是无返回值的,所以如果我们需要知道任务的执行结果,那么我们可以使用ScheduledExecutorService。

创建ScheduledExecutorService

使用Executors直接创建

代码语言:javascript复制
ScheduledExecutorService schedule = Executors.newScheduledThreadPool(20);

使用ScheduledThreadPoolExecutor自定义实现

代码语言:javascript复制
ScheduledExecutorService poolExecutor = new ScheduledThreadPoolExecutor(20,
            new MyThreadFactory("business-thread"), new MyThreadRejectExecutionHandler());

使用Executors创建ScheduledExecutorService,我们只需要传入核心线程数即可,如果我们需要自定义线程工厂ThreadFactory和拒绝策略RejectedExecutionHandler,那么我们可以使用ScheduledThreadPoolExecutor。

ScheduledExecutorService简单示例

代码语言:javascript复制
public class ScheduleThreadPool {
    private static final ScheduledExecutorService schedule = Executors.newScheduledThreadPool(20);

    private static String scheduleSayHello(){
        System.out.println("hello");
        return "hello";
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        /**
         * initialDelay初始延迟,也就是第一次启动的时候需要延迟多久才执行,之后会按照period周期执行
         */
        ScheduledFuture<?> scheduledFuture1 = schedule.scheduleAtFixedRate(ScheduleThreadPool::scheduleSayHello, 0, 3, TimeUnit.SECONDS);
        /**
         * Runnable command , 无返回值
         * delay:延迟多长时间执行,执行过一次后就不再执行
         */
        ScheduledFuture<String> scheduledFuture2 = ScheduleThreadPool.schedule.schedule(ScheduleThreadPool::scheduleSayHello, 3, TimeUnit.SECONDS);
        /**
         * 有返回值的定时线程池,
         * delay:延迟多长时间执行,执行过一次后就不再执行
         * Callable<V> callable有返回值,返回值为ScheduledFuture<?>,scheduledFuture.get()
         */
        ScheduledFuture<?> scheduledFuture3 = ScheduleThreadPool.schedule.schedule(ScheduleThreadPool::scheduleSayHello, 3, TimeUnit.SECONDS);
        /**
         * initialDelay初始延迟
         * delay : 执行周期
         */
        ScheduledFuture<?> scheduledFuture4 = ScheduleThreadPool.schedule.scheduleWithFixedDelay(ScheduleThreadPool::scheduleSayHello, 0, 3, TimeUnit.SECONDS);
    }
}

Spring @Scheduled

Spring提供了任务调度的注解,是我们能够很轻松的使用简单的任务调度,并且能够配置CRON表达式,使调度更加的灵活,我们如果使用SpringBoot,那么只需要在启动类上面加上@EnableScheduling注解,再在我们需要调度的方法上加上@Scheduled即可。

如下任务,我们在schedule()方法上标记了@Scheduled(cron = "0/2 * * * * ?"),我们使用了CRON表达式,那么此方法就会以两秒的固定速度反复执行此任务,当然,此注解下面有多种策略供我们使用,比如固定速率fixedRate

代码语言:javascript复制
/**
 * 功能说明:Spring任务调度
 * <p>
 * Original @Author: steakliu-刘牌, 2022-09-19  23:22
 * <p>
 * Copyright (C)2020-2022  steakliu All rights reserved.
 */
@Component
public class SpringScheduler {

    @Scheduled(cron = "0/2 * * * * ?")
    public void schedule(){
        System.out.println("start schedule ......");
    }
}

总结

从上面的三个个任务调度方式中,我们可以看出易用程度在不断的提供,功能也在不断地完善,特别对于Spring的scheduling,可以说使用起来是相当的方便,无需要去写代码,只需要标记注解,按照自己的需要来选择调度的策略就行。不过我们也能看出,上述的几种方式也只适用于单体结构的项目,并且没有可视化的显示,所有的任务配置都是写死的,无法动态调整,无法监控任务的成败并重试,总而言之,不够强大,不够灵活,不够丰富,面对分布式系统无能为力,所以我们从下一篇开始就介绍分布式任务调度器。

今天关于任务几个简单的任务调度方案的分享就到这里,接下来将会进入分布式认读调度的学习和使用,以及架构解剖,源码分析,感谢你的观看,我们下期见!

0 人点赞