前言
今天来分享一下任务调度,任务调度在我们项目中是不可避免的,只是不同的场景,不同的业务复杂程度和业务要求,我们会使用不同的任务调度实现,而任务调度的实现方式以及框架有很多,在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能够获取任务的执行结果,因为它的返回值是ScheduledFuture
,ScheduledFuture
继承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
。
/**
* 功能说明: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
,可以说使用起来是相当的方便,无需要去写代码,只需要标记注解,按照自己的需要来选择调度的策略就行。不过我们也能看出,上述的几种方式也只适用于单体结构的项目,并且没有可视化的显示,所有的任务配置都是写死的,无法动态调整,无法监控任务的成败并重试,总而言之,不够强大,不够灵活,不够丰富,面对分布式系统无能为力,所以我们从下一篇开始就介绍分布式任务调度器。
今天关于任务几个简单的任务调度方案的分享就到这里,接下来将会进入分布式认读调度的学习和使用,以及架构解剖,源码分析,感谢你的观看,我们下期见!