一、使用Spring Task
Spring 3.0以后自带了 task 调度工具,使用比 Quartz简单方便,使用 @Scheduled 注解。
1、创建一个 SpringBoot项目,引入spring-boot-starter-web依赖。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId> </dependency>
- 在启动类上添加 @EnableScheduling 注解,表示开启定时任务。
@SpringBootApplication @EnableScheduling public class QuartzApplication { public static void main(String[] args) { SpringApplication.run(QuartzApplication.class, args); } }
- 了解 @Scheduled 注解
在方法上使用 @Scheduled 注解表示开启一个定时任务:下面参数单位都是毫秒
- fixedRate:表示按一定频率来执行定时任务,具体是指两次任务的开始时间间隔,即第二次任务开始时,第一次任务可能还没结束。
- fixedDelay:表示按一定时间间隔来执行定时任务,具体是指本次任务结束到下次任务开始之间的时间间隔。该属性还可以配合initialDelay使用, 定义该任务延迟执行时间。
- initialDelay:表示首次任务启动的延迟时间。与fixedDelay配合使用。
- cron:通过 cron 表达式来配置任务执行时间,cron 表达式格式为:[秒] [分] [小时] [日] [月] [周] [年]
2、单线程执行任务
使用同一个线程中串行执行,如果只有一个定时任务,这样做肯定没问题,当定时任务增多,如果一个任务卡死,会导致其他任务也无法执行。
- 创建一个类,配置定时任务
@Component public class Task1 { @Scheduled(fixedRate = 2000) public void fixedRateTask() { System.out.println("fixedRateTask定时任务开始 : " + LocalDateTime.now().toLocalTime() + ",线程:" + Thread.currentThread().getName()); } @Scheduled(fixedDelay = 2000) public void fixedDelayTask1() { System.out.println("fixedDelayTask1111定时任务开始 : " + LocalDateTime.now().toLocalTime() + ",线程:" + Thread.currentThread().getName()); } @Scheduled(initialDelay = 2000,fixedDelay = 2000) public void initialDelayTask2() throws InterruptedException { System.out.println("initialDelayTask2222定时任务开始 : " + LocalDateTime.now().toLocalTime() + ",线程:" + Thread.currentThread().getName()); TimeUnit.SECONDS.sleep(15); } @Scheduled(cron = "0/5 * * * * ?") public void cron() { System.out.println("cron定时任务开始 : " + LocalDateTime.now().toLocalTime() + ",线程:" + Thread.currentThread().getName()); } }
- 启动项目,定时任务就开始工作了
- 可以看到使用的是同一个线程,并出现了任务阻塞的情况。
3、多线程执行任务
Spring Task 默认是单线程的,想要改成多线程,
给Spring Task提供一个多线程的TaskScheduler,Spring已经有默认实现。
- 方式一
创建配置类,@EnableAsync注解:表示开启异步事件的支持
@Configuration @EnableAsync // 开启异步事件的支持 public class AsyncTaskConfig { private int corePoolSize = 10; private int maxPoolSize = 200; private int queueCapacity = 10; @Bean public Executor taskExecutor() { // 线程池 ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(corePoolSize); executor.setMaxPoolSize(maxPoolSize); executor.setQueueCapacity(queueCapacity); executor.initialize(); return executor; } }
在定时任务的类或者方法上添加 @Async 注解。最后重启项目,每一个任务都是在不同的线程中
- 方式二
创建配置类
@Configuration @EnableAsync // 开启异步事件的支持 public class ScheduleConfig { @Bean("taskScheduler1") public TaskScheduler taskScheduler1(){ ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); scheduler.setPoolSize(10); scheduler.setThreadNamePrefix("spring-task1-thread"); return scheduler; } @Bean public TaskScheduler taskScheduler2(){ ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); scheduler.setPoolSize(10); scheduler.setThreadNamePrefix("spring-task2-thread"); return scheduler; } }
在定时任务的类或者方法上添加 @Async 注解。最后重启项目。
@Component public class TestTask2 { @Async("taskScheduler1") @Scheduled(cron = "0/1 * * * * ?") public void execute1() { System.out.println("execute1定时任务开始 : " + LocalDateTime.now().toLocalTime() + ",线程:" + Thread.currentThread().getName()); } @Async("taskScheduler2") @Scheduled(cron = "0/1 * * * * ?") public void execute2() throws InterruptedException { System.out.println("execute2定时任务开始 : " + LocalDateTime.now().toLocalTime() + ",线程:" + Thread.currentThread().getName()); } @Async("taskScheduler2") @Scheduled(cron = "0/1 * * * * ?") public void execute3() throws InterruptedException { System.out.println("execute3定时任务开始 : " + LocalDateTime.now().toLocalTime() + ",线程:" + Thread.currentThread().getName()); } @Scheduled(cron = "0/2 * * * * ?") public void execute4() throws InterruptedException { System.out.println("execute4定时任务开始 : " + LocalDateTime.now().toLocalTime() + ",线程:" + Thread.currentThread().getName()); } @Scheduled(cron = "0/3 * * * * ?") public void execute5() throws InterruptedException { System.out.println("execute5定时任务开始 : " + LocalDateTime.now().toLocalTime() + ",线程:" + Thread.currentThread().getName()); TimeUnit.SECONDS.sleep(15); } }
任务使用同上,线程池有区别。
二、使用第三方框架 Quartz
使用 @Scheduled 注解来解决简单的定时任务,大部分项目中可能都是使用 Quartz 来做定时任务。
Quartz是一个开源项目,专注于任务调度器,功能强大,提供了极为广泛的特性如持久化任务,集群和分布式任务等。 Quartz核心是调度器,还采用多线程管理。
- 持久化任务:当应用程序停止运行时,所有调度信息不被丢失,当你重新启动时,调度信息还存在,这就是持久化任务。
- 集群和分布式处理:当在集群环境下,当有配置Quartz的多个客户端(节点)时,
采用Quartz的集群和分布式处理时,简单了解几点
- 1)一个节点无法完成的任务,会被集群中拥有相同的任务的节点取代执行。
- 2)Quartz调度是通过触发器的类别来识别不同的任务,在不同的节点定义相同的触发器的类别,这样在集群下能稳定的运行,一个节点无法完成的任务,会被集群中拥有相同的任务的节点取代执行。
- 3)分布式 体现在 当相同的任务定时在一个时间点,在那个时间点,不会被两个节点同时执行。
1、添加 Quartz 依赖
在 上面的 SpringBoot项目(把@Scheduled相关的注释掉)中使用 Quartz ,添加 Quartz 依赖。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
在启动类上添加 @EnableScheduling 注解,表示开启定时任务。
@SpringBootApplication @EnableScheduling public class QuartzApplication { public static void main(String[] args) { SpringApplication.run(QuartzApplication.class, args); } }
2、Quartz的使用
Quartz 在使用过程中,有两个关键概念,
- 一个是 JobDetail(要做的事情),要定义 JobDetail,需要先自定义Job
- 一个是 Trigger触发器(什么时候做)
1. 定义 Job
@Service public class UserService { public String get(Long id){ return "UserService get data:" + id; } }
SpringBoot中实现定时任务(Quartz)(二)https://developer.aliyun.com/article/1393277