Quartz任务调度框架
Quartz是一个任务调度框架,用于定时执行任务。
任务调度:系统中有N的任务,分别需要在不同的时刻执行这些任务,这种多任务的执行策略就是任务调度
0 定时任务实现的方法
- spring schedule
- 优点:无需整合spring,作业类中就可以调用业务service
- 缺点:默认单线程执行任务,当前一个任务执行时间过长时,会使后面的任务延期执行,如果前一个任务的执行时间过长,可能会导致后面的任务执行时间不准确,最差的情况可能会丢任务;不能做数据存储型的定时任务;如果希望并发运行,需要配置线程池
- spring-quartz
- 优点:支持持久化
- 缺点:配置稍显复杂
定时任务持久化:当前定时任务保存在内存中,每当项目重启的时候,定时任务就会清空并重写加载,原来的一些执行信息就丢失了,我们需要在保存下定时任务的执行信息,下次重启项目的时候我们的定时任务就可以根据上次执行的状态再执行下一次的定时任务。
1.Spring schedule测试
使用默认的单线程
启动类 App.java
package org.example;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@Slf4j
@SpringBootApplication
@EnableScheduling // 启动 spring task
public class App
{
public static void main ( String[] args ) {
SpringApplication.run(App.class,args);
}
}
TaskComponent.java
package org.example.component;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class TaskComponent {
// 定时任务1 每1秒执行一次,执行时间为10秒
@Scheduled(cron = "*/1 * * * * ?")
public void task1() {
log.info("task1-------当先线程的名字={}, id={}", Thread.currentThread().getName(),Thread.currentThread().getId());
try {
Thread.sleep(1000 * 10);
// int s = 2/0; // 模拟任务执行中抛出异常
} catch (InterruptedException e) {
log.error("Thread sleep error {}", e.getMessage());
Thread.currentThread().interrupt();
}
}
// 定时任务2 每2秒执行一次
@Scheduled(cron = "*/2 * * * * ?")
public void task2() {
log.info("task2-------当先线程的名字={}, id={}", Thread.currentThread().getName(),Thread.currentThread().getId());
}
}
运行启动类,观察输出
2023-05-31 20:57:44.009 INFO 44860 --- [ scheduling-1] org.example.component.TaskComponent : task2-------当先线程的名字=scheduling-1, id=36
2023-05-31 20:57:44.009 INFO 44860 --- [ scheduling-1] org.example.component.TaskComponent : task1-------当先线程的名字=scheduling-1, id=36
2023-05-31 20:57:54.024 INFO 44860 --- [ scheduling-1] org.example.component.TaskComponent : task2-------当先线程的名字=scheduling-1, id=36
2023-05-31 20:57:55.016 INFO 44860 --- [ scheduling-1] org.example.component.TaskComponent : task1-------当先线程的名字=scheduling-1, id=36
2023-05-31 20:58:05.027 INFO 44860 --- [ scheduling-1] org.example.component.TaskComponent : task2-------当先线程的名字=scheduling-1, id=36
2023-05-31 20:58:06.002 INFO 44860 --- [ scheduling-1] org.example.component.TaskComponent : task1-------当先线程的名字=scheduling-1, id=36
task2的执行时间为44秒,54秒,05秒
可以看到task2由于task1任务的阻塞,导致task2不能按照预想的每2秒执行一次。
用的时候注意不要将定时任务的时间设置的过于接近。
使用多线程
需要做的事情:
- 启动类上加上
@EnableAsync
- task 上加上
@Async
运行启动类,观察输出
2023-05-31 21:10:37.025 INFO 28216 --- [ task-1] org.example.component.TaskComponent : task1-------当先线程的名字=task-1, id=39
2023-05-31 21:10:38.007 INFO 28216 --- [ task-2] org.example.component.TaskComponent : task1-------当先线程的名字=task-2, id=42
2023-05-31 21:10:38.008 INFO 28216 --- [ task-3] org.example.component.TaskComponent : task2-------当先线程的名字=task-3, id=43
2023-05-31 21:10:39.005 INFO 28216 --- [ task-4] org.example.component.TaskComponent : task1-------当先线程的名字=task-4, id=46
2023-05-31 21:10:40.002 INFO 28216 --- [ task-5] org.example.component.TaskComponent : task2-------当先线程的名字=task-5, id=48
2023-05-31 21:10:40.002 INFO 28216 --- [ task-6] org.example.component.TaskComponent : task1-------当先线程的名字=task-6, id=49
2023-05-31 21:10:41.014 INFO 28216 --- [ task-7] org.example.component.TaskComponent : task1-------当先线程的名字=task-7, id=52
2023-05-31 21:10:42.014 INFO 28216 --- [ task-3] org.example.component.TaskComponent : task1-------当先线程的名字=task-3, id=43
2023-05-31 21:10:42.014 INFO 28216 --- [ task-8] org.example.component.TaskComponent : task2-------当先线程的名字=task-8, id=55
2023-05-31 21:10:43.015 INFO 28216 --- [ task-5] org.example.component.TaskComponent : task1-------当先线程的名字=task-5, id=48
2023-05-31 21:10:44.009 INFO 28216 --- [ task-8] org.example.component.TaskComponent : task2-------当先线程的名字=task-8, id=55
可以看到两个任务的执行时间互不影响,并且线程id也不相同,说明是多线程执行任务。
但是,这种方式,会引发另外的问题,task1会多次执行。我们希望上一个task1未执行完成的时候,下一个task1不执行。
使用线程池
task类注释掉 @Async
,并且配置如下的配置类
package org.example.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
@Configuration
@Slf4j
public class TaskConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
// 为springtask配置线程池
taskRegistrar.setScheduler(taskExecutor());
}
// 创建线程池
@Bean
public Executor taskExecutor(){
return Executors.newScheduledThreadPool(10);
}
}
2023-05-31 21:24:18.007 INFO 29676 --- [pool-1-thread-1] org.example.component.TaskComponent : task2-------当先线程的名字=pool-1-thread-1, id=36
2023-05-31 21:24:18.007 INFO 29676 --- [pool-1-thread-1] org.example.component.TaskComponent : task2 执行完了,线程的名字=pool-1-thread-1, id=36
2023-05-31 21:24:18.008 INFO 29676 --- [pool-1-thread-2] org.example.component.TaskComponent : task1-------当先线程的名字=pool-1-thread-2, id=37
2023-05-31 21:24:20.002 INFO 29676 --- [pool-1-thread-1] org.example.component.TaskComponent : task2-------当先线程的名字=pool-1-thread-1, id=36
2023-05-31 21:24:20.002 INFO 29676 --- [pool-1-thread-1] org.example.component.TaskComponent : task2 执行完了,线程的名字=pool-1-thread-1, id=36
2023-05-31 21:24:22.009 INFO 29676 --- [pool-1-thread-3] org.example.component.TaskComponent : task2-------当先线程的名字=pool-1-thread-3, id=39
2023-05-31 21:24:22.009 INFO 29676 --- [pool-1-thread-3] org.example.component.TaskComponent : task2 执行完了,线程的名字=pool-1-thread-3, id=39
2023-05-31 21:24:24.006 INFO 29676 --- [pool-1-thread-1] org.example.component.TaskComponent : task2-------当先线程的名字=pool-1-thread-1, id=36
2023-05-31 21:24:24.006 INFO 29676 --- [pool-1-thread-1] org.example.component.TaskComponent : task2 执行完了,线程的名字=pool-1-thread-1, id=36
2023-05-31 21:24:26.006 INFO 29676 --- [pool-1-thread-4] org.example.component.TaskComponent : task2-------当先线程的名字=pool-1-thread-4, id=44
2023-05-31 21:24:26.006 INFO 29676 --- [pool-1-thread-4] org.example.component.TaskComponent : task2 执行完了,线程的名字=pool-1-thread-4, id=44
2023-05-31 21:24:28.015 INFO 29676 --- [pool-1-thread-3] org.example.component.TaskComponent : task2-------当先线程的名字=pool-1-thread-3, id=39
2023-05-31 21:24:28.015 INFO 29676 --- [pool-1-thread-2] org.example.component.TaskComponent : task1 执行完了,线程的名字=pool-1-thread-2, id=37
2023-05-31 21:24:28.015 INFO 29676 --- [pool-1-thread-3] org.example.component.TaskComponent : task2 执行完了,线程的名字=pool-1-thread-3, id=39
2023-05-31 21:24:29.011 INFO 29676 --- [pool-1-thread-5] org.example.component.TaskComponent : task1-------当先线程的名字=pool-1-thread-5, id=49
2023-05-31 21:24:30.010 INFO 29676 --- [pool-1-thread-1] org.example.component.TaskComponent : task2-------当先线程的名字=pool-1-thread-1, id=36
2023-05-31 21:24:30.010 INFO 29676 --- [pool-1-thread-1] org.example.component.TaskComponent : task2 执行完了,线程的名字=pool-1-thread-1, id=36
2023-05-31 21:24:32.008 INFO 29676 --- [pool-1-thread-6] org.example.component.TaskComponent : task2-------当先线程的名字=pool-1-thread-6, id=54
2023-05-31 21:24:32.008 INFO 29676 --- [pool-1-thread-6] org.example.component.TaskComponent : task2 执行完了,线程的名字=pool-1-thread-6, id=54
2023-05-31 21:24:34.001 INFO 29676 --- [pool-1-thread-6] org.example.component.TaskComponent : task2-------当先线程的名字=pool-1-thread-6, id=54
2023-05-31 21:24:34.001 INFO 29676 --- [pool-1-thread-6] org.example.component.TaskComponent : task2 执行完了,线程的名字=pool-1-thread-6, id=54
观察日志输出,可以看出task1是上一个执行结束之后,下一个task1才会继续执行
2.Spring-quartz框架
导入依赖
<!-- quartz 定时调度框架 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
QuartzTask1.java
package org.example.component;
import lombok.extern.slf4j.Slf4j;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.stereotype.Component;
@Slf4j
@Component
@DisallowConcurrentExecution
public class QuartzTask2 extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
log.info("task2任务执行开始,线程id={}", Thread.currentThread().getId());
log.info("task2任务执行结束,线程id={}", Thread.currentThread().getId());
}
}
QuartzTask2.java
package org.example.component;
import lombok.extern.slf4j.Slf4j;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.stereotype.Component;
@Slf4j
@Component
@DisallowConcurrentExecution
public class QuartzTask2 extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
log.info("task2任务执行开始,线程id={}", Thread.currentThread().getId());
log.info("task2任务执行结束,线程id={}", Thread.currentThread().getId());
}
}
QuartzConfig.java
package org.example.config;
import lombok.extern.slf4j.Slf4j;
import org.example.component.QuartzTask1;
import org.example.component.QuartzTask2;
import org.quartz.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Slf4j
@Configuration
public class QuartzConfig {
@Bean
public JobDetail task1_job() {
// JobDetail 需要加入 storeDurably() 不然会报错
return JobBuilder.newJob(QuartzTask1.class).storeDurably().build();
}
@Bean
public JobDetail task2_job() {
return JobBuilder.newJob(QuartzTask2.class).storeDurably().build();
}
@Bean
public Trigger task1_trigger() {
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("*/1 * * * * ?");
return TriggerBuilder.newTrigger()
.forJob(task1_job())
.withIdentity("task1")
.withSchedule(scheduleBuilder)
.build();
}
@Bean
public Trigger task2_trigger() {
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("*/2 * * * * ?");
return TriggerBuilder.newTrigger()
.forJob(task2_job())
.withIdentity("task2")
.withSchedule(scheduleBuilder)
.build();
}
}
2023-05-31 21:53:42.012 INFO 50100 --- [eduler_Worker-2] org.example.component.QuartzTask2 : task2任务执行开始,线程id=25
2023-05-31 21:53:42.012 INFO 50100 --- [eduler_Worker-2] org.example.component.QuartzTask2 : task2任务执行结束,线程id=25
2023-05-31 21:53:44.013 INFO 50100 --- [eduler_Worker-3] org.example.component.QuartzTask2 : task2任务执行开始,线程id=26
2023-05-31 21:53:44.013 INFO 50100 --- [eduler_Worker-3] org.example.component.QuartzTask2 : task2任务执行结束,线程id=26
2023-05-31 21:53:46.010 INFO 50100 --- [eduler_Worker-4] org.example.component.QuartzTask2 : task2任务执行开始,线程id=27
2023-05-31 21:53:46.010 INFO 50100 --- [eduler_Worker-4] org.example.component.QuartzTask2 : task2任务执行结束,线程id=27
2023-05-31 21:53:48.004 INFO 50100 --- [eduler_Worker-5] org.example.component.QuartzTask2 : task2任务执行开始,线程id=28
2023-05-31 21:53:48.004 INFO 50100 --- [eduler_Worker-5] org.example.component.QuartzTask2 : task2任务执行结束,线程id=28
2023-05-31 21:53:50.003 INFO 50100 --- [eduler_Worker-6] org.example.component.QuartzTask2 : task2任务执行开始,线程id=29
2023-05-31 21:53:50.003 INFO 50100 --- [eduler_Worker-6] org.example.component.QuartzTask2 : task2任务执行结束,线程id=29
2023-05-31 21:53:51.823 INFO 50100 --- [eduler_Worker-1] org.example.component.QuartzTask1 : task1任务执行结束,线程id=24
2023-05-31 21:53:51.824 INFO 50100 --- [eduler_Worker-7] org.example.component.QuartzTask1 : task1任务执行开始,线程id=30
2023-05-31 21:53:52.017 INFO 50100 --- [eduler_Worker-8] org.example.component.QuartzTask2 : task2任务执行开始,线程id=31
2023-05-31 21:53:52.017 INFO 50100 --- [eduler_Worker-8] org.example.component.QuartzTask2 : task2任务执行结束,线程id=31
2023-05-31 21:53:54.008 INFO 50100 --- [eduler_Worker-9] org.example.component.QuartzTask2 : task2任务执行开始,线程id=32
2023-05-31 21:53:54.008 INFO 50100 --- [eduler_Worker-9] org.example.component.QuartzTask2 : task2任务执行结束,线程id=32
2023-05-31 21:53:56.012 INFO 50100 --- [duler_Worker-10] org.example.component.QuartzTask2 : task2任务执行开始,线程id=33
2023-05-31 21:53:56.012 INFO 50100 --- [duler_Worker-10] org.example.component.QuartzTask2 : task2任务执行结束,线程id=33
2023-05-31 21:53:58.009 INFO 50100 --- [eduler_Worker-2] org.example.component.QuartzTask2 : task2任务执行开始,线程id=25
2023-05-31 21:53:58.010 INFO 50100 --- [eduler_Worker-2] org.example.component.QuartzTask2 : task2任务执行结束,线程id=25
2023-05-31 21:54:00.009 INFO 50100 --- [eduler_Worker-3] org.example.component.QuartzTask2 : task2任务执行开始,线程id=26
2023-05-31 21:54:00.009 INFO 50100 --- [eduler_Worker-3] org.example.component.QuartzTask2 : task2任务执行结束,线程id=26
2023-05-31 21:54:01.826 INFO 50100 --- [eduler_Worker-7] org.example.component.QuartzTask1 : task1任务执行结束,线程id=30
2023-05-31 21:54:01.827 INFO 50100 --- [eduler_Worker-4] org.example.component.QuartzTask1 : task1任务执行开始,线程id=27
2023-05-31 21:54:02.002 INFO 50100 --- [eduler_Worker-5] org.example.component.QuartzTask2 : task2任务执行开始,线程id=28
2023-05-31 21:54:02.003 INFO 50100 --- [eduler_Worker-5] org.example.component.QuartzTask2 : task2任务执行结束,线程id=28
2023-05-31 21:54:04.011 INFO 50100 --- [eduler_Worker-6] org.example.component.QuartzTask2 : task2任务执行开始,线程id=29
2023-05-31 21:54:04.011 INFO 50100 --- [eduler_Worker-6] org.example.component.QuartzTask2 : task2任务执行结束,线程id=29
2023-05-31 21:54:06.004 INFO 50100 --- [eduler_Worker-1] org.example.component.QuartzTask2 : task2任务执行开始,线程id=24
2023-05-31 21:54:06.004 INFO 50100 --- [eduler_Worker-1] org.example.component.QuartzTask2 : task2任务执行结束,线程id=24
2023-05-31 21:54:08.012 INFO 50100 --- [eduler_Worker-8] org.example.component.QuartzTask2 : task2任务执行开始,线程id=31
2023-05-31 21:54:08.012 INFO 50100 --- [eduler_Worker-8] org.example.component.QuartzTask2 : task2任务执行结束,线程id=31
2023-05-31 21:54:10.011 INFO 50100 --- [eduler_Worker-9] org.example.component.QuartzTask2 : task2任务执行开始,线程id=32
2023-05-31 21:54:10.012 INFO 50100 --- [eduler_Worker-9] org.example.component.QuartzTask2 : task2任务执行结束,线程id=32
2023-05-31 21:54:11.828 INFO 50100 --- [eduler_Worker-4] org.example.component.QuartzTask1 : task1任务执行结束,线程id=27
2023-05-31 21:54:11.828 INFO 50100 --- [duler_Worker-10] org.example.component.QuartzTask1 : task1任务执行开始,线程id=33
通过日志输出,可以看到,quartz达到了,springtask使用线程池的效果。
单个任务的串行执行,使用的注解
@DisallowConcurrentExecution
不允许并行执行
quartz的核心一共有三个类
- 任务 Job :需要执行的任务逻辑类,需要实现 Job 接口的 execute 方法
- 触发器 Trigger :执行任务的调度器。例如,每天的00:00执行任务A,那么就需要设置Trigger的属性
- 调度器 Scheduler : 任务的调度器,将任务 Job和触发器Trigger整合起来,负责基于Trigger设定的时间执行任务Job