您的位置:首页 > 编程语言 > Java开发

补习系列(9)-springboot 定时器,你用对了吗

2019-08-23 15:07 429 查看
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://blog.csdn.net/devcloud/article/details/100038369

目录

  • 四、@Async
  • 小结
  • 简介

    大多数的应用程序都离不开定时器,通常在程序启动时、运行期间会需要执行一些特殊的处理任务。
    比如资源初始化、数据统计等等,SpringBoot 作为一个灵活的框架,有许多方式可以实现定时器或异步任务。
    我总结了下,大致有以下几种:

      1. 使用 JDK 的 TimerTask
      1. 使用 JDK 自带调度线程池
      1. 使用 Quartz 调度框架
      1. 使用 @Scheduled 、@Async 注解

    其中第一种使用 TimerTask 的方法已经不建议使用,原因是在系统时间跳变时TimerTask存在挂死的风险
    第三种使用 Quartz 调度框架可以实现非常强大的定时器功能,包括分布式调度定时器等等。
    考虑作为大多数场景使用的方式,下面的篇幅将主要介绍 第二、第四种。

    一、应用启动任务

    在 SpringBoot 应用程序启动时,可以通过以下两个接口实现初始化任务:

    1. CommandLineRunner
    2. ApplicationRunner

    两者的区别不大,唯一的不同在于:
    CommandLineRunner 接收一组字符串形式的进程命令启动参数;
    ApplicationRunner 接收一个经过解析封装的参数体对象。

    详细的对比看下代码:

    [code]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
    初始化一个线程池的代码如下:

    [code]    /**
    * 构造调度线程池
    *
    * @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来实现:

    [code]@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 注解启用该功能。

    代码如下:

    [code]/**
    * 利用@Scheduled注解实现定时器
    *
    * @author atp
    
    3ff7
    *
    */
    @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 接口。

    代码如下:

    [code]    @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 注解使用。

    代码如下:

    [code]    @Configuration
    @EnableAsync
    public static class ScheduleConfig {
    
    }

    使用 @Async 实现模拟任务

    [code]@Component
    public 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,将提前返回。

    执行结果如下:

    [code]- async timer execute in 2 ms
    - async task execute in 3154 ms

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

    定制 @Async 线程池

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

    代码如下:

    [code]    @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 几个常用的注解进行了说明,
    并提供定制其线程池的方法,希望对读者能有一定帮助。

    欢迎继续关注"美码师的补习系列-springboot篇" ,如果觉得老司机的文章还不赖,请多多分享转发^-^

    作者:zale

    内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
    标签: