1.主要总结@Scheduled()注解的三个属性:cron,fixedRate,fixedDelay
cron为cron表达式,用来表示该任务在日期时间维度执行频率,详细可参考cron表达式的文章
fixedRate:代表该任务的执行频率,单位毫秒,无论任务执行耗时多久,总是以该频率执行任务
fixedDelay:代表该任务的执行频率,单位毫秒,在上一次任务执行完后等待x毫秒后执行下次任务
2.开启异步@EnableAsync后使用上述三种属性产生的效果
前提:springboot通过scheduling实现的定时任务使用的线程有自己的线程池,默认大小为1,此时无法满足多个定时任务的情形,因此通常需要手动设置线程池的大小,以10为例。
有以下三种情形
1.使用fixedRate属性不加@Async注解、使用fixedRate属性加@Async注解
2.使用cron不加@Async注解、使用cron属性加@Async注解
3.使用fixedDelay不加@Async注解
@Component
@EnableAsync
@Slf4j
public class ScheduledTasks {
@Scheduled(fixedDelay = 3000)
// @Scheduled(fixedRate = 3000)
// @Scheduled(cron = "0/3 * * * * ?")
// @Async(value = "myAsync")
void contextLoads() throws InterruptedException {
log.info("scheduled1 wait start");
Thread.sleep(4000);
log.info("scheduled1 wait end");
log.info("scheduled task run.... " + Thread.currentThread().getName());
}
// @Scheduled(cron = "0/3 * * * * ?")
@Scheduled(fixedDelay = 3000)
// @Scheduled(fixedRate = 3000)
// @Async(value = "myAsync")
void scheduled2() throws InterruptedException {
log.info("scheduled2 wait start");
Thread.sleep(4000);
log.info("scheduled2 wait end");
log.info("scheduled task2 run.... " + Thread.currentThread().getName());
}
}
3.结论:
1.使用fixedRate加@Async注解和使用cron加@Async注解效果相同
会实现无论任务执行耗时多久,下次任务始终会以规定的频率执行;
上述例子中若为fixedRate,则下次任务开始执行始终是在上次任务开始后的三秒;
若为cron,则下次任务开始执行始终为上次任务开始执行后的三秒,也就是上次任务开始时间为10:23:02,则下次任务开始时间为10:23:05,此种情况不受任务执行耗时影响。
2.使用fixedRate不加@Async注解
此时若每次任务执行耗时比fixedRate的值小,则会在上次任务开始后的fixedRate毫秒后开始执行下次任务;
若每次执行任务耗时比fixedRate的值大,则会在上次任务执行后立刻执行下次任务,因为此时已经超过规定的执行频率的时间,因此需要立刻执行下次任务。
3.使用cron不加@Async注解
此时若每次任务执行耗时比cron表达式所表示的上次任务开始时间与下次任务开始时间的间隔小,则会以cron表达式表示的频率执行任务;
若每次任务执行耗时比cron表达式所表示的上次任务开始时间与下次任务开始时间的间隔大,则下次任务会在上次任务执行完成后再到达cron表达式所表示的任务开始执行的时刻开始执行任务,例如上述例子中上次任务开始时间为10:23:02,任务执行耗时4s,而以cron表达式表示的频率下次的执行时间为10:23:05,但是此时上次任务并没有执行完成,上次任务执行完成的时间为10:23:06,因此下次任务执行需要等到cron表达式表示的下一次任务执行的时刻也就是10:23:08.
4.使用fixedDelay不加@Async注解
使用fixedDelay属性时不能加@Async注解,否则会报错。
此时无论任务执行耗时是多少,下次任务开始执行的时间总是上次任务执行完成后的fixedDelay毫秒。
5.当有多个定时任务时,在保证定时任务线程池数以及异步线程池数足够的情况下,多个定时任务同样遵循上述结论。
6.上述结论中1和4是比较符合开发需要的两种场景。
4.修改定时任务线程池以及自定义异步任务线程池
1.修改定时任务默认的线程池大小
方式一:修改配置文件application.yml
spring.task.scheduling.pool.size = 10
方式二:配置类
@Configuration
public class ScheduleConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(Executors.newScheduledThreadPool(50));
}
}
2.自定义定时任务的线程池
@Configuration
public class ScheduleConfig {
/**
* 此处方法名为Bean的名字,方法名无需固定
* 因为是按TaskScheduler接口自动注入
*/
@Bean
public TaskScheduler taskScheduler(){
// Spring提供的定时任务线程池类
ThreadPoolTaskScheduler taskScheduler=new ThreadPoolTaskScheduler();
//设定最大可用的线程数目
taskScheduler.setPoolSize(10);
return taskScheduler;
}
}
3.自定义异步任务线程池
@Component
public class AsyncScheduledTaskConfig {
@Bean
public Executor myAsync() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//最大线程数
executor.setMaxPoolSize(100);
//核心线程数
executor.setCorePoolSize(10);
//任务队列的大小
executor.setQueueCapacity(10);
//线程前缀名
executor.setThreadNamePrefix("task-thread");
//线程存活时间
executor.setKeepAliveSeconds(30);
/**
* 拒绝处理策略
* CallerRunsPolicy():交由调用方线程运行,比如 main 线程。
* AbortPolicy():直接抛出异常。
* DiscardPolicy():直接丢弃。
* DiscardOldestPolicy():丢弃队列中最老的任务。
*/
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
//线程初始化
executor.initialize();
return executor;
}
}
使用时只需在@Async注解中使用value属性指定线程线程池名称,也就是bean的名称,上述默认为方法名