简介
大多数的应用程序都离不开定时器,通常在程序启动时、运行期间会需要执行一些特殊的处理任务。
比如资源初始化、数据统计等等,SpringBoot 作为一个灵活的框架,有许多方式可以实现定时器或异步任务。
我总结了下,大致有以下几种:
使用 JDK 的 TimerTask
使用 JDK 自带调度线程池
使用 Quartz 调度框架
使用 @Scheduled 、@Async 注解
其中第一种使用 TimerTask 的方法已经不建议使用,原因是在系统时间跳变时TimerTask存在挂死的风险。
第三种使用 Quartz 调度框架可以实现非常强大的定时器功能,包括分布式调度定时器等等。
考虑作为大多数场景使用的方式,下面的篇幅将主要介绍 第二、第四种。
在 SpringBoot 应用程序启动时,可以通过以下两个接口实现初始化任务:
CommandLineRunner
ApplicationRunner
两者的区别不大,唯一的不同在于:
CommandLineRunner 接收一组字符串形式的进程命令启动参数;
ApplicationRunner 接收一个经过解析封装的参数体对象。
详细的对比看下代码:
public class CommandLines { private static final Logger logger = LoggerFactory.getLogger(CommandLines.class); @Component @Order(1) public static class CommandLineAppStartupRunner implements CommandLineRunner { @Override public void run(String... args) throws Exception { logger.info( "[CommandLineRunner]Application started with command-line arguments: {} .To kill this application, press Ctrl + C.", Arrays.toString(args)); } } @Component @Order(2) public static class AppStartupRunner implements ApplicationRunner { @Override public void run(ApplicationArguments args) throws Exception { logger.info("[ApplicationRunner]Your application started with option names : {}", args.getOptionNames()); } } } 二、JDK 自带调度线程池为了实现定时调度,需要用到 ScheduledThreadpoolExecutor
初始化一个线程池的代码如下:
可以将 corePoolSize 指定为大于1,以实现定时任务的并发执行。
为了在 SpringBoot 项目中使用,我们利用一个CommandLineRunner来实现:
@Component @Order(1) public class ExecutorTimer implements CommandLineRunner { private static final Logger logger = LoggerFactory.getLogger(ExecutorTimer.class); private ScheduledExecutorService schedulePool; @Override public void run(String... args) throws Exception { logger.info("start executor tasks"); schedulePool = ThreadPools.newSchedulingPool(2); schedulePool.scheduleWithFixedDelay(new Runnable() { @Override public void run() { logger.info("run on every minute"); } }, 5, 60, TimeUnit.SECONDS); } }schedulePool.scheduleWithFixedDelay 指定了调度任务以固定的频率执行。
三、@Scheduled@Scheduled 是 Spring3.0 提供的一种基于注解实现调度任务的方式。
在使用之前,需要通过 @EnableScheduling 注解启用该功能。
代码如下:
/** * 利用@Scheduled注解实现定时器 * * @author atp * */ @Component public class ScheduleTimer { private static final Logger logger = LoggerFactory.getLogger(ScheduleTimer.class); /** * 每10s */ @Scheduled(initialDelay = 5000, fixedDelay = 10000) public void onFixDelay() { logger.info("schedule job on every 10 seconds"); } /** * 每分钟的0秒执行 */ @Scheduled(cron = "0 * * * * *") public void onCron() { logger.info("schedule job on every minute(0 second)"); } /** * 启用定时器配置 * * @author atp * */ @Configuration @EnableScheduling public static class ScheduleConfig { } }说明
上述代码中展示了两种定时器的使用方式:
第一种方式
指定初始延迟(initialDelay)、固定延迟(fixedDelay);
第二种方式
通过 cron 表达式定义
这与 unix/linux 系统 crontab 的定义类似,可以实现非常灵活的定制。
一些 cron 表达式的样例:
表达式 说明0 0 * * * * 每天的第一个小时
/10 * * * * 每10秒钟
0 0 8-10 * * * 每天的8,9,10点钟整点
0 * 6,19 * * * 每天的6点和19点每分钟
0 0/30 8-10 * * * 每天8:00, 8:30, 9:00, 9:30 10:00
0 0 9-17 * * MON-FRI 工作日的9点到17点
0 0 0 25 12 ? 每年的圣诞夜午夜
定制 @Scheduled 线程池
默认情况下,@Scheduled 注解的任务是由一个单线程的线程池进行调度的。
这样会导致应用内的定时任务只能串行执行。
为了实现定时任务并发,或是更细致的定制,
可以使用 SchedulingConfigurer 接口。