现在的位置: 首页 > 综合 > 正文

SpringBoot 定时器如何做

2020年02月21日 综合 ⁄ 共 5261字 ⁄ 字号 评论关闭

  大多数的应用程序都离不开定时器,通常在程序启动时、运行期间会需要执行一些特殊的处理任务。

  比如资源初始化、数据统计等等,SpringBoot 作为一个灵活的框架,有许多方式可以实现定时器或异步任务。

  其中第一种使用 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。

  初始化一个线程池的代码如下:

  /** * 构造调度线程池 * * @param corePoolSize * @param poolName * @return */ public static ScheduledThreadPoolExecutor newSchedulingPool(int corePoolSize, String poolName) { ScheduledThreadPoolExecutor threadPoolExecutor = new ScheduledThreadPoolExecutor(corePoolSize); // 设置变量 if (!StringUtils.isEmpty(poolName)) { threadPoolExecutor.setThreadFactory(new ThreadFactory() { @Override public Thread newThread(Runnable r) { Thread tr = new Thread(r, poolName + r.hashCode()); return tr; } }); } return threadPoolExecutor; }

  可以将 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 * */@Componentpublic 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 接口。

  代码如下:

  @Configuration @EnableScheduling public class ScheduleConfig implements SchedulingConfigurer { @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { taskRegistrar.setScheduler(taskExecutor()); } @Bean(destroyMethod="shutdown") public Executor taskExecutor() { //线程池大小 return Executors.newScheduledThreadPool(50); }

  四、@Async 注解

  @Async 注解的意义在于将 Bean方法的执行方式改为异步方式。 比如 在前端请求处理时,能通过异步执行提前返回结果。

  类似的,该注解需要配合 @EnableAsync 注解使用。

  代码如下:

  @Configuration @EnableAsync public static class ScheduleConfig { }

  使用 @Async 实现模拟任务

  @Componentpublic class AsyncTimer implements CommandLineRunner { private static final Logger logger = LoggerFactory.getLogger(AsyncTimer.class); @Autowired private AsyncTask task; @Override public void run(String... args) throws Exception { long t1 = System.currentTimeMillis(); task.doAsyncWork(); long t2 = System.currentTimeMillis(); logger.info("async timer execute in {} ms", t2 - t1); } @Component public static class AsyncTask { private static final Logger logger = LoggerFactory.getLogger(AsyncTask.class); @Async public void doAsyncWork() { long t1 = System.currentTimeMillis(); try { Thread.sleep((long) (Math.random() * 5000)); } catch (InterruptedException e) { } long t2 = System.currentTimeMillis(); logger.info("async task execute in {} ms", t2 - t1); } }

  示例代码中,AsyncTask 等待一段随机时间后结束。 而 AsyncTimer 执行了 task.doAsyncWork,将提前返回。

  执行结果如下:

  - async timer execute in 2 ms- async task execute in 3154 ms

  这里需要注意一点,异步的实现,其实是通过 Spring 的 AOP 能力实现的。 对于 AsyncTask 内部方法间的调用却无法达到效果。

  定制 @Async 线程池

  对于 @Async 线程池的定制需使用 AsyncConfigurer接口。

  代码如下:

  @Configuration @EnableAsync public static class ScheduleConfig implements AsyncConfigurer { @Bean public ThreadPoolTaskScheduler taskScheduler() { ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); //线程池大小 scheduler.setPoolSize(60); scheduler.setThreadNamePrefix("AsyncTask-"); scheduler.setAwaitTerminationSeconds(60); scheduler.setWaitForTasksToCompleteOnShutdown(true); return scheduler; } @Override public Executor getAsyncExecutor() { return taskScheduler(); } @Override public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { return null; } }

  小结

  定时异步任务是应用程序通用的诉求,本文收集了几种常见的实现方法。 作为 SpringBoot 应用来说,使用注解是最为便捷的。

  在这里我们对 @Scheduled、@Async 几个常用的注解进行了说明, 并提供定制其线程池的方法,希望对读者能有一定帮助。

抱歉!评论已关闭.