目录
一、SpringBoot+@Scheduled注解实现定时任务
1-1启用定时任务功能
1-2 添加定时任务
1-2-1 cron表达式
1-2-2 固定间隔定时任务
1-2-3 固定频率定时任务
1-3 并行执行定时任务
1-3-1 修改任务调度器 默认使用的线程池
1-3-2 交给异步线程池处理
一、SpringBoot+@Scheduled注解实现定时任务
当使用SpringBoot框架时,可以使用@Scheduled注解来实现定时任务
1-1启用定时任务功能
在启动类或者配置类上增加@EnableScheduling注解
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); } }
1-2 添加定时任务
@Schedule注解支持cron表达式、固定时间、固定频率三种调度方式
1-2-1 cron表达式
与Linux下定时任务用到的Cron表达式一样
@Scheduled(cron = "0/1 * * * * * *") public void mytimer(){ System.out.println("hello world"); }
注意:
【1】cron表达式配置的任务如果执行超时,会从上一个任务结束的时间开始计算间隔
【2】定时器的任务方法不能有返回值
【3】实现类上要有组件的注解@Component,@Service,@Repository
1-2-2 固定间隔定时任务
下一次的任务执行时间是从上一次定时任务结束时间开始计算
@Scheduled(fixedDelay = 2) public void mytimer(){ System.out.println("hello world"); }
1-2-3 固定频率定时任务
下一次的任务执行时间是从上一次定时任务开始时间开始计算
@Scheduled(@Scheduled(fixedRate = 2000) public void mytimer(){ System.out.println("hello world"); }
1-3 并行执行定时任务
默认状态下,只有一个线程在执行定时任务。
当有多有定时任务时,是串行的,不是并行的!!!
如有两个定时任务,分别设置每两秒执行一次,效果会是这样
Timer1:第0秒执行第一次
Timer2:第2秒执行第一次
Timer1:第4秒执行第二次
Timer2:第6秒执行第二次
.....
这并不是我们想要的效果,所以必须配置定时任务并行执行
1-3-1 修改任务调度器 默认使用的线程池
添加一个configuration,实现SchedulingConfigurer接口就可以了
@Configuration public class ScheduleConfig implements SchedulingConfigurer{ ? @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { taskRegistrar.setTaskScheduler(getTaskScheduler()); } ? @Bean public TaskScheduler getTaskScheduler() { ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler(); taskScheduler.setPoolSize(3); // 此处为线程池数量 taskScheduler.setThreadNamePrefix("myworker-"); taskScheduler.setWaitForTasksToCompleteOnShutdown(true); return taskScheduler; } }
1-3-2 交给异步线程池处理
启用@EnableAsync注解,并在每一个定时任务方法上使用@Async注解
@Component @EnableScheduling @EnableAsync public class MyCronTask { ? private static final Logger logger = LoggerFactory.getLogger(MyCronTask.class); ? @Async @Scheduled(fixedDelay = 2000) void task1Schedule() throws Exception{ Thread.sleep(2000); logger.info("task1 execute"); } ?@Async @Scheduled(fixedDelay = 2000) void task2Schedule() throws Exception{ Thread.sleep(2000); logger.info("task2 execute"); } ?@Async @Scheduled(fixedDelay = 2000) void task3Schedule() throws Exception{ Thread.sleep(2000); logger.info("task3 execute"); } }
@Async注解在使用时,如果不指定线程池的名称,则使用Spring默认的线程池,Spring默认的线程池为SimpleAsyncTaskExecutor
这个线程池的配置是
默认核心线程数:8, 最大线程数:Integet.MAX_VALUE, 队列使用LinkedBlockingQueue, 容量是:Integet.MAX_VALUE, 空闲线程保留时间:60s, 线程池拒绝策略:AbortPolicy。
可以看到,最大线程数使用的是Integer.MAX_VALUE,即对于每一次定时任务的执行都会创建新的线程,并发环境下,会无限创建进程 -> OOM -> 系统崩溃
为了更好控制线程的使用,我们可以自定义线程池。
首先定义一个线程池
@Configuration public class MyTaskExecutor { ? @Bean(name = "myExecutor") public TaskExecutor getMyExecutor() { ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); taskExecutor.setCorePoolSize(3); taskExecutor.setMaxPoolSize(10); taskExecutor.setQueueCapacity(20); taskExecutor.setThreadNamePrefix("myExecutor-"); taskExecutor.initialize(); return taskExecutor; } }
然后在使用@Async注解时指定线程池
@Component @EnableScheduling @EnableAsync public class MyCronTask { ? private static final Logger logger = LoggerFactory.getLogger(MyCronTask.class); ? @Async("myExecutor") @Scheduled(fixedDelay = 2000) void task1Schedule() throws Exception{ Thread.sleep(2000); logger.info("task1 execute"); } @Async("myExecutor") @Scheduled(fixedDelay = 2000) void task2Schedule() throws Exception{ Thread.sleep(2000); logger.info("task2 execute"); } @Async("myExecutor") @Scheduled(fixedDelay = 2000) void task3Schedule() throws Exception{ Thread.sleep(2000); logger.info("task3 execute"); } }