简介
说明
本文用示例介绍SpringBoot的@Scheduled注解的用法。
执行时间的配置
在方法上使用@Scheduled注解来设置任务的执行时间,并且使用三种属性配置方式:
| 项 | fixedRate | fixedDelay | cron |
| 描述 | 每隔多少时间就启动任务,不管该任务是否启动完成。单位:默认为毫秒 | 每次执行任务完成之后间隔多久再次执行该任务。单位:默认为毫秒 | 根据cron表达式执行 |
| 执行时机 | 项目启动时即开始 | 项目启动时即开始 | 根据cron表达式执行 |
initialDelay:指定第一次执行的延时时间。
如:@Scheduled(initialDelay = 1000, fixedRate = 3000) :第一次在延迟1秒后执行,之后按fixedRate的规则每 3 秒执行一次。
配置文件中指定cron
假如application.yml中指定:custom.schedule.cron.task1: 0/5 * * * * *
代码中可以这么写:@Scheduled(cron = “${custom.schedule.cron.task1}”)
配置文件中指定fixedDelay
假如application.yml中指定:custom.schedule.fixed-delay.task11: 1000
代码中可以这么写:@Scheduled(fixedDelayString = “${custom.schedule.fixed-delay.task1}”)
基础实例
前提条件
添加SpringBoot依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
启用定时器(主类添加@EnableScheduling)
@SpringBootApplication
@EnableScheduling
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
示例1
@Component
public class MyTest {
@Scheduled(cron = "0/5 * * * * *")
public void scheduled() {
System.out.println("this is cron");
}
}
执行结果
this is cron: 1589472135001
示例2
@Component
public class MyTest {
@Scheduled(cron = "0/5 * * * * *")
public void scheduled1() {
System.out.println("this is cron: " + System.currentTimeMillis());
}
@Scheduled(fixedRate = 5000)
public void scheduled2(){
System.out.println("this is fixedRate: " + System.currentTimeMillis());
}
@Scheduled(fixedDelay = 5000)
public void scheduled3(){
System.out.println("this is fixedDelay: " + System.currentTimeMillis());
}
}
执行结果(它们是串行执行的)
this is cron: 1589472145001 this is fixedRate: 1589472146341 this is fixedDelay: 1589472146342 this is cron: 1589472150002 this is fixedRate: 1589472151342 this is fixedDelay: 1589472151343 this is cron: 1589472155001 this is fixedRate: 1589472156342 this is fixedDelay: 1589472156343
单线程问题
问题描述
简介
@Scheduled默认是单线程执行的,如果前一个任务执行时间过长,则后一个任务会被延迟,示例如下:
启动类
package com.knife.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@EnableScheduling
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
测试类
package com.knife.example.schedule;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class DemoSchedule {
@Scheduled(cron = "0/1 * * * * ?")
public void task1() {
log.info("task1");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Scheduled(cron = "0/1 * * * * ?")
public void task2() {
log.info("task2");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
执行结果:(前一个任务执行完,才会执行下一个任务)
2024-07-07 14:58:39.013 INFO 19280 --- [ scheduling-1] com.knife.example.schedule.DemoSchedule : task1 2024-07-07 14:58:44.020 INFO 19280 --- [ scheduling-1] com.knife.example.schedule.DemoSchedule : task2 2024-07-07 14:58:49.035 INFO 19280 --- [ scheduling-1] com.knife.example.schedule.DemoSchedule : task1 2024-07-07 14:58:54.038 INFO 19280 --- [ scheduling-1] com.knife.example.schedule.DemoSchedule : task2 2024-07-07 14:58:59.050 INFO 19280 --- [ scheduling-1] com.knife.example.schedule.DemoSchedule : task1 2024-07-07 14:59:04.054 INFO 19280 --- [ scheduling-1] com.knife.example.schedule.DemoSchedule : task2 2024-07-07 14:59:09.067 INFO 19280 --- [ scheduling-1] com.knife.example.schedule.DemoSchedule : task1
解决方案1:配置线程数量
配置定时任务线程池的线程个数。
修改application.yml
spring:
task:
scheduling:
pool:
size: 3
shutdown:
# 定时线程池关闭时等待所有任务完成
await-termination: true
或者:
package com.knife.example.schedule;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import java.util.concurrent.Executors;
@Configuration
public class ScheduleConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(Executors.newScheduledThreadPool(3));
}
}
结果:并行执行,每个任务都会等上一个任务结束再执行。
2024-07-07 15:03:33.007 INFO 11452 --- [ scheduling-2] com.knife.example.schedule.DemoSchedule : task2 2024-07-07 15:03:33.007 INFO 11452 --- [ scheduling-1] com.knife.example.schedule.DemoSchedule : task1 2024-07-07 15:03:39.003 INFO 11452 --- [ scheduling-1] com.knife.example.schedule.DemoSchedule : task2 2024-07-07 15:03:39.003 INFO 11452 --- [ scheduling-2] com.knife.example.schedule.DemoSchedule : task1 2024-07-07 15:03:45.014 INFO 11452 --- [ scheduling-1] com.knife.example.schedule.DemoSchedule : task1 2024-07-07 15:03:45.014 INFO 11452 --- [ scheduling-3] com.knife.example.schedule.DemoSchedule : task2
解决方案2:自定义定时任务的线程池
@Scheduled的默认线程池是:ThreadPoolTaskScheduler这个Bean,最终调用到ThreadPoolExecutor的runWorker(Worker w)。
如果自定义了TaskScheduler,那么就会使用你自定义的,不会使用默认的这个ThreadPoolTaskScheduler。
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.concurrent.CustomizableThreadFactory;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
@Slf4j
@Configuration
public class ScheduledConfig {
private Integer poolSize = 10;
@Bean
public ThreadPoolTaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setThreadFactory(new CustomizableThreadFactory("my-scheduled-task-"));
// 设置线程池大小
scheduler.setPoolSize(poolSize);
// 线程名称前缀
scheduler.setThreadNamePrefix("scheduled-");
scheduler.setAwaitTerminationSeconds(60);
scheduler.setWaitForTasksToCompleteOnShutdown(true);
// 自定义RejectedExecutionHandler
scheduler.setRejectedExecutionHandler(this::rejectHandler);
return scheduler;
}
private void rejectHandler(Runnable r, ThreadPoolExecutor executor) {
log.error("定时任务线程池饱和");
// executor.getQueue().clear();
// executor.purge();
}
}
结果跟上边一样。
解决方案3:异步
用法:启动类加@EnableAsync,@Scheduled类或者方法上加@Async。注意:此法会使 fixedDelay策略失效。
@Async会开启异步,使用新线程去执行任务。详见:Spring-@Async异步执行的方法 – 自学精灵。
启动类:
package com.knife.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
@EnableAsync
@EnableScheduling
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
测试类:
package com.knife.example.schedule;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Slf4j
@Component
@Async
public class DemoSchedule {
@Scheduled(cron = "0/1 * * * * ?")
public void task1() {
log.info("task1");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Scheduled(cron = "0/1 * * * * ?")
public void task2() {
log.info("task2");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
结果:并行执行,真正按照cron一秒一次!
2024-07-07 15:08:34.010 INFO 14068 --- [ task-2] com.knife.example.schedule.DemoSchedule : task2 2024-07-07 15:08:34.010 INFO 14068 --- [ task-1] com.knife.example.schedule.DemoSchedule : task1 2024-07-07 15:08:35.015 INFO 14068 --- [ task-3] com.knife.example.schedule.DemoSchedule : task1 2024-07-07 15:08:35.015 INFO 14068 --- [ task-4] com.knife.example.schedule.DemoSchedule : task2 2024-07-07 15:08:36.005 INFO 14068 --- [ task-5] com.knife.example.schedule.DemoSchedule : task1 2024-07-07 15:08:36.005 INFO 14068 --- [ task-6] com.knife.example.schedule.DemoSchedule : task2

请先 !