Java中定时任务ScheduledThreadPoolExecutor、Timer、@Scheduled和Quartz

1.简介

在实际应用中,有时候我们需要创建一些个延迟的、并具有周期性的任务,比如,我们希望当我们的程序启动后每隔1小时就去做一次日志记录、每天凌晨12点去清理一下数据库中的过期数据等。在JDK中提供了两种方法去创建延迟周期性任务。分别是ScheduledThreadPoolExecutor和Timer。另外还有一个开源的更加强大的任务调度框架Quartz。下面我们来具体认识这三个框架。


2.Timer

Timer的主要方法有:

// 安排在指定的时间执行
void schedule(TimerTask task, Date time);

// 安排在指定的时间开始以重复的延时执行
void schedule(TimerTask task, Date firstTime, long period);

// 安排在指定的延迟后执行
void schedule(TimerTask task, long delay);

// 安排在指定的延迟后开始以重复的延时执行
void schedule(TimerTask task, long delay, long period);

// 安排在指定的时间开始以重复的速率执行
void scheduleAtFixedRate(TimerTask task, Date firstTime, long period);

// 安排在指定的延迟后开始以重复的速率执行
void scheduleAtFixedRate(TimerTask task, long delay, long period);

//可以在任何时刻调用cancel方法终止timer线程
cancel();

//从任务队列中删除已取消的任务,返回删除的数量
int purge();

注:重复的延时和重复的速率的区别在于,前者是在前一个任务的执行结束后间隔period时间再开始下一次执行;而scheduleAtFixedRate则是会尽量按照任务的初始时间来按照间隔period时间执行。如果一次任务执行由于某些原因被延迟了,用schedule()调度的后续任务同样也会被延迟,而用scheduleAtFixedRate()则会快速的开始两次或者多次执行,是后续任务的执行时间能够赶上来。


3.ScheduledThreadPoolExecutor

**ScheduledThreadPoolExecutor的主要方法:
 

// 在指定的延迟后执行
<V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit)

// 在指定的延迟后执行
ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)

// 在指定的延迟后以固定速率执行
ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)

// 在指定的延迟后以固定间隔执行
ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)

关于ScheduledThreadPoolExecutor,我在线程池的文章中已经详细描述过,大家可以参考Java中的线程池

如果是想使用JDK中的定时任务,那么推荐使用ScheduledThreadPoolExecutor而不是Timer。


4.Spring中定时任务@Scheduled

Spring Task不是独立的项目,是spring-context 模块下提供的定时任务工具,是Spring3.0以后自带的task,可以将它看成一个轻量级的Quartz。

使用Spring Task有两种方式,一种是基于注解****@Scheduled,一种是基于配置文件。在springboot中推荐使用注解和配置类的方式,这里我们主要使用注解和配置类,基于配置文件的也会给出demo。

基于注解

在springboot的启动类上通过注解@EnableScheduling开启。然后在类的方法上通过@Scheduled注解使用,代码案例如下:

@Component
public class ScheduleTest {


    @Scheduled(fixedDelayString = "5000")
   public void testFixedDelayString() {
        System.out.println("Execute at " + System.currentTimeMillis());
    }
}

基于注解的使用可以参考我之前的文章,很详细:@scheduled注解的使用

基于xml配置

首先是任务类:

public class SpringTask {

    private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public void m1(){
        System.out.println("m1:"+simpleDateFormat.format(new Date()));
    }

    public void m2(){
        System.out.println("m2:"+simpleDateFormat.format(new Date()));
    }

    public void m3(){
        System.out.println("m2:"+simpleDateFormat.format(new Date()));
    }
}

然后是xml配置:

<!--spring-task.xml配置-->
<bean id="springTask" class="com.njit.springtask.SpringTask"></bean>
   <!--注册调度任务-->
   <task:scheduled-tasks>
       <!--延迟8秒 执行任务-->
       <!--<task:scheduled ref="springTask" method="m1" fixed-delay="8000" />-->

       <!--固定速度5秒 执行任务-->
       <!--<task:scheduled ref="springTask" method="m2" fixed-rate="5000"/>-->

       <!--
           使用cron表达式 指定触发时间
           spring task 只支持6位的cron表达式 秒 分 时 日 月 星期
       -->
       <task:scheduled ref="springTask" method="m3" cron="50-59 * * ? * *"/>
   </task:scheduled-tasks>

   <!--执行器配置-->
   <task:executor id="threadPoolTaskExecutor" pool-size="10" keep-alive="5"></task:executor>

   <!--调度器配置-->
   <task:scheduler id="threadPoolTaskScheduler" pool-size="10"></task:scheduler>

Spring Task 任务执行器以及调度器解释

实现原理

spring task对定时任务的两个抽象:

TaskExecutor:与jdk中Executor相同,引入的目的是为定时任务的执行提供线程池的支持,如果不设置**,默认只有一个线程。**

TaskScheduler:提供定时任务支持,需要传入一个Runnable的任务做为参数,并指定需要周期执行的时间或者触发器,这样Runnable任务就可以周期性执行了。

继承关系如下:

任务执行器与调度器的实现类分别为ThreadPoolTaskExecutor、ThreadPoolTaskScheduler

TaskScheduler需要传入一个Runnable的任务做为参数,并指定需要周期执行的时间或者触发器(Trigger)。

spring定义了Trigger接口的实现类CronTrigger,支持使用cron表达式指定定时策略,使用如下:

scheduler.schedule(task, new CronTrigger("30 * * * * ?"));

在springboot项目中,我们一般都是使用@schedule注解来使用spring task,这个注解内部的实现就是使用上面的内容。

spring在初始化bean后,通过postProcessAfterInitialization拦截到所有的用到**@Scheduled**注解的方法,并解析相应的的注解参数,放入“定时任务列表”等待后续处理;之后再“定时任务列表”中统一执行相应的定时任务(任务为顺序执行,先执行cron,之后再执行fixedRate)

具体的源码解析可参考:Spring中的Scheduling模块
@Schedule注解源码解析
总结
定时任务@Scheduled之单线程多线程问题

SpringBoot使用@scheduled定时执行任务的时候是在一个单线程中,如果有多个任务,其中一个任务执行时间过长,则有可能会导致其他后续任务被阻塞直到该任务执行完成。也就是会造成一些任务无法定时执行的错觉。

可以通过如下代码进行测试:

@Scheduled(cron = "0/1 * * * * ? ")
public void deleteFile() throws InterruptedException {
    log.info("111delete success, time:" + new Date().toString());
    Thread.sleep(1000 * 5);//模拟长时间执行,比如IO操作,http请求
}
 
@Scheduled(cron = "0/1 * * * * ? ")
public void syncFile() {
    log.info("222sync success, time:" + new Date().toString());
}

2021-08-25 23:38:27.008  INFO 18972 --- [   scheduling-1] com.njit.springtask.ScheduleTest         : 111delete success, time:Wed Aug 25 23:38:27 CST 2021
2021-08-25 23:38:32.010  INFO 18972 --- [   scheduling-1] com.njit.springtask.ScheduleTest         : 222sync success, time:Wed Aug 25 23:38:32 CST 2021
2021-08-25 23:38:33.004  INFO 18972 --- [   scheduling-1] com.njit.springtask.ScheduleTest         : 111delete success, time:Wed Aug 25 23:38:33 CST 2021
2021-08-25 23:38:38.007  INFO 18972 --- [   scheduling-1] com.njit.springtask.ScheduleTest         : 222sync success, time:Wed Aug 25 23:38:38 CST 2021
2021-08-25 23:38:39.007  INFO 18972 --- [   scheduling-1] com.njit.springtask.ScheduleTest         : 111delete success, time:Wed Aug 25 23:38:39 CST 2021
2021-08-25 23:38:44.014  INFO 18972 --- [   scheduling-1] com.njit.springtask.ScheduleTest         : 222sync success, time:Wed Aug 25 23:38:44 CST 2021
2021-08-25 23:38:45.015  INFO 18972 --- [   scheduling-1] com.njit.springtask.ScheduleTest         : 111delete success, time:Wed Aug 25 23:38:45 CST 2021
2021-08-25 23:38:50.027  INFO 18972 --- [   scheduling-1] com.njit.springtask.ScheduleTest         : 222sync success, time:Wed Aug 25 23:38:50 CST 2021
2021-08-25 23:38:51.001  INFO 18972 --- [   scheduling-1] com.njit.springtask.ScheduleTest         : 111delete success, time:Wed Aug 25 23:38:51 CST 2021

上面的日志中可以明显的看到syncFile被阻塞了,直达deleteFile执行完它才执行了
而且从日志信息中也可以看出@Scheduled是使用了一个线程池中的一个单线程来执行所有任务的。

/**如果把Thread.sleep(1000*5)注释了,输出如下:

2021-08-25 23:40:12.003  INFO 17988 --- [   scheduling-1] com.njit.springtask.ScheduleTest         : 222sync success, time:Wed Aug 25 23:40:12 CST 2021
2021-08-25 23:40:12.004  INFO 17988 --- [   scheduling-1] com.njit.springtask.ScheduleTest         : 111delete success, time:Wed Aug 25 23:40:12 CST 2021
2021-08-25 23:40:13.015  INFO 17988 --- [   scheduling-1] com.njit.springtask.ScheduleTest         : 111delete success, time:Wed Aug 25 23:40:13 CST 2021
2021-08-25 23:40:13.015  INFO 17988 --- [   scheduling-1] com.njit.springtask.ScheduleTest         : 222sync success, time:Wed Aug 25 23:40:13 CST 2021
2021-08-25 23:40:14.015  INFO 17988 --- [   scheduling-1] com.njit.springtask.ScheduleTest         : 111delete success, time:Wed Aug 25 23:40:14 CST 2021
2021-08-25 23:40:14.015  INFO 17988 --- [   scheduling-1] com.njit.springtask.ScheduleTest         : 222sync success, time:Wed Aug 25 23:40:14 CST 2021
2021-08-25 23:40:15.011  INFO 17988 --- [   scheduling-1] com.njit.springtask.ScheduleTest         : 111delete success, time:Wed Aug 25 23:40:15 CST 2021
2021-08-25 23:40:15.011  INFO 17988 --- [   scheduling-1] com.njit.springtask.ScheduleTest         : 222sync success, time:Wed Aug 25 23:40:15 CST 2021
2021-08-25 23:40:16.005  INFO 17988 --- [   scheduling-1] com.njit.springtask.ScheduleTest         : 111delete success, time:Wed Aug 25 23:40:16 CST 2021
2021-08-25 23:40:16.005  INFO 17988 --- [   scheduling-1] com.njit.springtask.ScheduleTest         : 222sync success, time:Wed Aug 25 23:40:16 CST 2021
2021-08-25 23:40:17.002  INFO 17988 --- [   scheduling-1] com.njit.springtask.ScheduleTest         : 111delete success, time:Wed Aug 25 23:40:17 CST 2021
2021-08-25 23:40:17.002  INFO 17988 --- [   scheduling-1] com.njit.springtask.ScheduleTest         : 222sync success, time:Wed Aug 25 23:40:17 CST 2021

查看源码,从ScheduledAnnotationBeanPostProcessor类开始一路找下去。果然,在ScheduledTaskRegistrar(定时任务注册类)中的ScheduleTasks中又这样一段判断:

if (this.taskScheduler == null) {
    this.localExecutor = Executors.newSingleThreadScheduledExecutor();
    this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
}

这就说明如果taskScheduler为空,那么就给定时任务做了一个单线程的线程池,正好在这个类中还有一个设置taskScheduler的方法:

public void setScheduler(Object scheduler) {
    Assert.notNull(scheduler, "Scheduler object must not be null");
    if (scheduler instanceof TaskScheduler) {
        this.taskScheduler = (TaskScheduler) scheduler;
    }
    else if (scheduler instanceof ScheduledExecutorService) {
        this.taskScheduler = new ConcurrentTaskScheduler(((ScheduledExecutorService) scheduler));
    }
    else {
        throw new IllegalArgumentException("Unsupported scheduler type: " + scheduler.getClass());
    }
}

解决办法

    1、扩大原定时任务线程池中的核心线程数

@Configuration
public class ScheduleConfig implements SchedulingConfigurer {
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.setScheduler(Executors.newScheduledThreadPool(50));
    }
}

这个方法,在程序启动后,会逐步启动50个线程,放在线程池中。每个定时任务每次执行会占用1个线程。但是相同的定时任务,执行的时候,还是在同一个线程中。
例如,程序启动,每个定时任务占用一个线程。任务1开始执行,任务2也开始执行。如果任务1卡死了,那么下个周期,任务1还是处理卡死状态,任务2可以正常执行。也就是说,任务1某一次卡死了,不会影响其他线程,但是他自己本身这个定时任务会一直等待上一次任务执行完成!

按照上面配置后,启动任务

@Component
@Slf4j
public class ScheduleTest {


    @Scheduled(cron = "0/1 * * * * ? ")
    public void deleteFile() throws InterruptedException {
        log.info(Thread.currentThread().getName()+"  线程 1111111delete success, time:" + new Date().toString());
        Thread.sleep(1000 * 10);//模拟长时间执行,比如IO操作,http请求
    }

    @Scheduled(cron = "0/1 * * * * ? ")
    public void syncFile() throws InterruptedException {
        log.info(Thread.currentThread().getName()+"  线程 22222222sync success, time:" + new Date().toString());
        Thread.sleep(1000 * 10);//模拟长时间执行,比如IO操作,http请求
    }
}

日志如下:

2021-08-26 22:54:58.006  INFO 1776 --- [pool-1-thread-2] com.njit.springtask.ScheduleTest         : pool-1-thread-2  线程 1111111delete success, time:Thu Aug 26 22:54:58 CST 2021
2021-08-26 22:54:58.006  INFO 1776 --- [pool-1-thread-1] com.njit.springtask.ScheduleTest         : pool-1-thread-1  线程 22222222sync success, time:Thu Aug 26 22:54:58 CST 2021
2021-08-26 22:55:09.003  INFO 1776 --- [pool-1-thread-1] com.njit.springtask.ScheduleTest         : pool-1-thread-1  线程 22222222sync success, time:Thu Aug 26 22:55:09 CST 2021
2021-08-26 22:55:09.003  INFO 1776 --- [pool-1-thread-2] com.njit.springtask.ScheduleTest         : pool-1-thread-2  线程 1111111delete success, time:Thu Aug 26 22:55:09 CST 2021
2021-08-26 22:55:20.005  INFO 1776 --- [pool-1-thread-3] com.njit.springtask.ScheduleTest         : pool-1-thread-3  线程 1111111delete success, time:Thu Aug 26 22:55:20 CST 2021
2021-08-26 22:55:20.005  INFO 1776 --- [pool-1-thread-4] com.njit.springtask.ScheduleTest         : pool-1-thread-4  线程 22222222sync success, time:Thu Aug 26 22:55:20 CST 2021
2021-08-26 22:55:31.004  INFO 1776 --- [pool-1-thread-2] com.njit.springtask.ScheduleTest         : pool-1-thread-2  线程 1111111delete success, time:Thu Aug 26 22:55:31 CST 2021
2021-08-26 22:55:31.004  INFO 1776 --- [pool-1-thread-1] com.njit.springtask.ScheduleTest         : pool-1-thread-1  线程 22222222sync success, time:Thu Aug 26 22:55:31 CST 2021
2021-08-26 22:55:42.011  INFO 1776 --- [pool-1-thread-3] com.njit.springtask.ScheduleTest         : pool-1-thread-3  线程 1111111delete success, time:Thu Aug 26 22:55:42 CST 2021
2021-08-26 22:55:42.011  INFO 1776 --- [pool-1-thread-6] com.njit.springtask.ScheduleTest         : pool-1-thread-6  线程 22222222sync success, time:Thu Aug 26 22:55:42 CST 2021
2021-08-26 22:55:53.002  INFO 1776 --- [pool-1-thread-4] com.njit.springtask.ScheduleTest         : pool-1-thread-4  线程 1111111delete success, time:Thu Aug 26 22:55:53 CST 2021
2021-08-26 22:55:53.002  INFO 1776 --- [pool-1-thread-7] com.njit.springtask.ScheduleTest         : pool-1-thread-7  线程 22222222sync success, time:Thu Aug 26 22:55:53 CST 2021
2021-08-26 22:56:04.002  INFO 1776 --- [pool-1-thread-8] com.njit.springtask.ScheduleTest         : pool-1-thread-8  线程 22222222sync success, time:Thu Aug 26 22:56:04 CST 2021
2021-08-26 22:56:04.002  INFO 1776 --- [pool-1-thread-5] com.njit.springtask.ScheduleTest         : pool-1-thread-5  线程 1111111delete success, time:Thu Aug 26 22:56:04 CST 2021
2021-08-26 22:56:15.006  INFO 1776 --- [pool-1-thread-1] com.njit.springtask.ScheduleTest         : pool-1-thread-1  线程 1111111delete success, time:Thu Aug 26 22:56:15 CST 2021
2021-08-26 22:56:15.006  INFO 1776 --- [pool-1-thread-2] com.njit.springtask.ScheduleTest         : pool-1-thread-2  线程 22222222sync success, time:Thu Aug 26 22:56:15 CST 2021
2021-08-26 22:56:26.001  INFO 1776 --- [pool-1-thread-9] com.njit.springtask.ScheduleTest         : pool-1-thread-9  线程 1111111delete success, time:Thu Aug 26 22:56:26 CST 2021
2021-08-26 22:56:26.001  INFO 1776 --- [ool-1-thread-10] com.njit.springtask.ScheduleTest         : pool-1-thread-10  线程 22222222sync success, time:Thu Aug 26 22:56:26 CST 2021
2021-08-26 22:56:37.016  INFO 1776 --- [pool-1-thread-6] com.njit.springtask.ScheduleTest         : pool-1-thread-6  线程 22222222sync success, time:Thu Aug 26 22:56:37 CST 2021
2021-08-26 22:56:37.016  INFO 1776 --- [pool-1-thread-3] com.njit.springtask.ScheduleTest         : pool-1-thread-3  线程 1111111delete success, time:Thu Aug 26 22:56:37 CST 2021
2021-08-26 22:56:48.014  INFO 1776 --- [ool-1-thread-11] com.njit.springtask.ScheduleTest         : pool-1-thread-11  线程 1111111delete success, time:Thu Aug 26 22:56:48 CST 2021

    2、把Scheduled配置成多线程执行

@Configuration
public class ScheduleConfig {
 
    @Bean
    public TaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
        taskScheduler.setPoolSize(50);
        return taskScheduler;
    }
}

@EnableScheduling
public class TaskFileScheduleService {
 
    @Scheduled(cron="0 */1 * * * ?")
    public void task1(){
    .......
    }
    
    @Scheduled(cron="0 */1 * * * ?")
    public void task2(){
    .......
    }

这种方法,程序启动,每个定时任务占用一个线程。任务1开始执行,任务2也开始执行。如果任务1卡死了,那么下个周期,任务1还是处理卡死状态,任务2可以正常执行。也就是说,任务1某一次卡死了,不会影响其他线程,但是他自己本身这个定时任务会一直等待上一次任务执行完成!这种方案和第一种的表现一样!

    3、使用@Async注解

@Configuration
@EnableAsync
public class ScheduleConfig {

}

@EnableScheduling
public class TaskFileScheduleService {
 
    @Async
    @Scheduled(cron="0 */1 * * * ?")
    public void task1(){
    .......
    }
    
    @Async
    @Scheduled(cron="0 */1 * * * ?")
    public void task2(){
    .......
    }

这种方法,每次定时任务启动的时候,都会创建一个单独的线程来处理。也就是说同一个定时任务也会启动多个线程处理。
例如:任务1和任务2一起处理,但是线程1卡死了,任务2是可以正常执行的。且下个周期,任务1还是会正常执行,不会因为上一次卡死了,影响任务1。
但是任务1中的卡死线程越来越多,最终会把线程池占满,还是会影响到定时任务。(@Async异步方法默认使用Spring创建ThreadPoolTaskExecutor。默认核心线程数:8,最大线程数:Integet.MAX_VALUE,队列使用LinkedBlockingQueue,容量是:Integet.MAX_VALUE,空闲线程保留时间:60s,线程池拒绝策略:AbortPolicy。)

可以通过如下配置@Async的线程池参数:

#核心线程数
spring.task.execution.pool.core-size=200
#最大线程数
spring.task.execution.pool.max-size=1000
#空闲线程保留时间
spring.task.execution.pool.keep-alive=3s
#队列容量
spring.task.execution.pool.queue-capacity=1000
#线程名称前缀
spring.task.execution.thread-name-prefix=test-thread-

    4、将@Scheduled注释的方法内部改成异步执行

//当然了,构建一个合理的线程池也是一个关键,否则提交的任务也会在自己构建的线程池中阻塞
    ExecutorService service = Executors.newFixedThreadPool(5);
 
    @Scheduled(cron = "0/1 * * * * ? ")
    public void deleteFile() {
        service.execute(() -> {
            log.info("111delete success, time:" + new Date().toString());
            try {
                Thread.sleep(1000 * 5);//改成异步执行后,就算你再耗时也不会印象到后续任务的定时调度了
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
    }
 
    @Scheduled(cron = "0/1 * * * * ? ")
    public void syncFile() {
        service.execute(()->{
            log.info("222sync success, time:" + new Date().toString());
        });
    }

5.Quartz

首先我们要了解一下quartz中的一些基本概念:

    Scheduler:任务调度器,是实际执行任务调度的控制器。在spring中通过SchedulerFactoryBean封装起来。

    Trigger:触发器,用于定义任务调度的时间规则,有SimpleTrigger,CronTrigger,DateIntervalTrigger等,其中CronTrigger用的比较多,本文主要介绍这种方式。CronTrigger在spring中封装在CronTriggerFactoryBean中。

    SimpleTrigger:简单触发器,从某个时间开始,每隔多少时间触发,重复多少次。

    CronTrigger:使用cron表达式定义触发的时间规则,如"0 0 0,2,4 1/1 * ? *" 表示每天的0,2,4点触发。

    DailyTimeIntervalTrigger:每天中的一个时间段,每N个时间单元触发,时间单元可以是毫秒,秒,分,小时

    CalendarIntervalTrigger:每N个时间单元触发,时间单元可以是毫秒,秒,分,小时,日,月,年。

    Calendar:它是一些日历特定时间点的集合。一个trigger可以包含多个Calendar,以便排除或包含某些时间点。

    JobDetail:用来描述Job实现类及其它相关的静态信息,如Job名字、关联监听器等信息。在spring中有JobDetailFactoryBean和 MethodInvokingJobDetailFactoryBean两种实现,如果任务调度只需要执行某个类的某个方法,就可以通过MethodInvokingJobDetailFactoryBean来调用。

    Job:是一个接口,只有一个方法void execute(JobExecutionContext context),开发者实现该接口定义运行任务,JobExecutionContext类提供了调度上下文的各种信息。Job运行时的信息保存在JobDataMap实例中。实现Job接口的任务,默认是无状态的,若要将Job设置成有状态的(即是否支持并发),在quartz中是给实现的Job添加@DisallowConcurrentExecution注解

    Quartz 任务调度的核心元素是 scheduler, trigger 和 job,其中 trigger 和 job 是任务调度的元数据, scheduler 是实际执行调度的控制器。

    在 Quartz 中,trigger 是用于定义调度时间的元素,即按照什么时间规则去执行任务。Quartz 中主要提供了四种类型的 trigger:SimpleTrigger,CronTirgger,DailyTimeIntervalTrigger,和 CalendarIntervalTrigger

    在 Quartz 中,job 用于表示被调度的任务。主要有两种类型的 job:无状态的(stateless)和有状态的(stateful)。对于同一个 trigger 来说,有状态的 job 不能被并行执行,只有上一次触发的任务被执行完之后,才能触发下一次执行。Job 主要有两种属性:volatility 和 durability,其中 volatility 表示任务是否被持久化到数据库存储,而 durability 表示在没有 trigger 关联的时候任务是否被保留。两者都是在值为 true 的时候任务被持久化或保留。一个 job 可以被多个 trigger 关联,但是一个 trigger 只能关联一个 job

使用

    引入starter依赖

  <!-- quartz -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-quartz</artifactId>
        </dependency>

    编写两个任务Task

/**
 * @author
 * 任务一
 */
public class TestTask1 extends QuartzJobBean{

    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println("TestQuartz01----" + sdf.format(new Date()));
    }
}


/**
 * 任务二
 * @author
 */
public class TestTask2 extends QuartzJobBean{
    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println("TestQuartz02----" + sdf.format(new Date()));
    }
}
 

    编写配置类

/**
 * quartz的配置类
 */
@Configuration
public class QuartzConfig {

    @Bean
    public JobDetail testQuartz1() {
        return JobBuilder.newJob(TestTask1.class).withIdentity("testTask1").storeDurably().build();
    }

    @Bean
    public Trigger testQuartzTrigger1() {
        //5秒执行一次
        SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
                .withIntervalInSeconds(5)
                .repeatForever();
        return TriggerBuilder.newTrigger().forJob(testQuartz1())
                .withIdentity("testTask1")
                .withSchedule(scheduleBuilder)
                .build();
    }

    @Bean
    public JobDetail testQuartz2() {
        return JobBuilder.newJob(TestTask2.class).withIdentity("testTask2").storeDurably().build();
    }

    @Bean
    public Trigger testQuartzTrigger2() {
        //cron方式,每隔5秒执行一次
        return TriggerBuilder.newTrigger().forJob(testQuartz2())
                .withIdentity("testTask2")
                .withSchedule(CronScheduleBuilder.cronSchedule("*/5 * * * * ?"))
                .build();
    }


}

    启动任务观察

可以正常的看到任务正常启动,任务Task被执行:

image-20210116214659029
6.几个框架之间的对比
Timer和ScheduledThreadPoolExecutor

    Timer对调度的支持是基于绝对时间的,因此任务对系统时间的改变是敏感的;而ScheduledThreadPoolExecutor支持相对时间。

    Timer使用单线程方式来执行所有的TimerTask,如果某个TimerTask很耗时则会影响到其他TimerTask的执行;而ScheduledThreadPoolExecutor则可以构造一个固定大小的线程池来执行任务。

    Timer不会捕获由TimerTask抛出的未检查异常,故当有异常抛出时,Timer会终止,导致未执行完的TimerTask不再执行,新的TimerTask也不能被调度;ScheduledThreadPoolExecutor对这个问题进行了妥善的处理,不会影响其他任务的执行。
————————————————
版权声明:本文为CSDN博主「njitzyd」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_37687594/article/details/119943278

猜你喜欢

转载自blog.csdn.net/qq_16504067/article/details/131555359
今日推荐