zoukankan      html  css  js  c++  java
  • SpringBoot定时任务Scheduled

    在日常项目运行中,我们总会有需求在某一时间段周期性的执行某个动作。比如每天在某个时间段导出报表,或者每隔多久统计一次现在在线的用户量。在springboot中可以有很多方案去帮我们完成定时器的工作,有Java自带的java.util.Timer类,也有强大的调度器Quartz,还有SpringBoot自带的Scheduled,今天主要说说Scheduled。

    一、定时器比较

    框架名称Cron表达式固定间隔执行固定频率执行任务持久化难易度

    TimerTask

    不支持 支持 支持 不支持 一般

    schedule

    支持 支持 支持 不支持 简单

    Quartz

    支持 支持 支持 支持

    在实际应用中,如果没有分布式场景(quartz 支持分布式, schedule 不支持(需要自己实现,用分布式锁,哪个拿到了这把锁,哪个就行执行),schedule跟spring结合的更好,还是很适用的。

    二、SpringBoot的定时任务

    使用SpringBoot创建定时任务非常简单,目前主要有以下三种创建方式:

    • 基于注解
    • 基于SchedulingConfigurer接口,我们可以从数据库中读取指定时间来动态执行定时任务
    • 基于注解设定多线程定时任务

    1. 基于注解@Scheduled

    注解@Scheduled默认为单线程,开启多个任务时,任务的执行时机会受上一个任务执行时间的影响。即多个任务都是在同个线程中先后执行。

    @Configuration      //标识为配置类
    @EnableScheduling   // 开启定时任务
    public class ScheduleTask {
        //3.添加定时任务
        @Scheduled(cron = "0/5 * * * * ?")
        //或直接指定时间间隔,例如:5秒
        //@Scheduled(fixedRate=5000)
        private void configureTasks() {
            System.err.println("执行静态定时任务时间: " + LocalDateTime.now());
        }
    }

    @Scheduled除了支持灵活的参数表达式cron之外,还支持简单的延时操作,例如 fixedDelay ,fixedRate 填写相应的毫秒数即可。

    cron表达式详解:https://www.cnblogs.com/myitnews/p/11863041.html

    2. 基于SchedulingConfigurer接口

    @Schedule注解的一个缺点就是其定时时间不能动态更改,它适用于具有固定任务周期的任务,若要修改任务执行周期,只能走“停服务→修改任务执行周期→重启服务”这条路。而基于 SchedulingConfigurer 接口方式可以做到。SchedulingConfigurer 接口可以实现在@Configuration 类上,同时不要忘了,还需要@EnableScheduling 注解的支持。

    接口源码:

    @FunctionalInterface
    public interface SchedulingConfigurer {
        void configureTasks(ScheduledTaskRegistrar var1);
    }

    ScheduledTaskRegistrar类包括以下几个重要方法:

    • void addTriggerTask(Runnable task, Trigger trigger) 
    • void addTriggerTask(TriggerTask task)
    • void addCronTask(Runnable task, String expression)
    • void addCronTask(CronTask task)
    • void addFixedRateTask(Runnable task, long interval)
    • void addFixedRateTask(IntervalTask task)
    • void addFixedDelayTask(Runnable task, long delay)
    • void addFixedDelayTask(IntervalTask task)
    @Component
    @EnableScheduling
    public class TestTask implements SchedulingConfigurer {
     
        @Override
        public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
            taskRegistrar.addTriggerTask(new Runnable() {
                @Override
                public void run() {
                    // 定时任务要执行的内容
                    System.out.println("【开始执行定时任务。。。】");
                }
            }, new Trigger() {
                @Override
                public Date nextExecutionTime(TriggerContext triggerContext) {
                    // 定时任务触发,可修改定时任务的执行周期
                    String cron = "0 0/5 * * * ?"; //可以将表达式配置在数据库中
                    CronTrigger trigger = new CronTrigger(cron);
                    Date nextExecDate = trigger.nextExecutionTime(triggerContext);
                    return nextExecDate;
                }
            });
        }
    }

    注意: 如果在数据库修改时格式出现错误,则定时任务会停止,即使重新修改正确;此时只能重新启动项目才能恢复。

    如果确实可能存在数据库表达式错误的,可以是使用org.springframework.scheduling.support.CronSequenceGenerator的isValidExpression验证下,错误的给个默认执行表达式。

    3. 多线程定时任务

    多线程定时任务是基于@Async注解的。

    @Component
    @EnableScheduling   // 1.开启定时任务
    @EnableAsync        // 2.开启多线程
    public class MultithreadScheduleTask {
    
        @Async
        @Scheduled(fixedDelay = 1000)  //间隔1秒
        public void first() throws InterruptedException {
            System.out.println("第一个定时任务开始 : " + LocalDateTime.now().toLocalTime() + "
    线程 : " + Thread.currentThread().getName());
            System.out.println();
            Thread.sleep(1000 * 10);
        }
    
        @Async
        @Scheduled(fixedDelay = 2000)
        public void second() {
            System.out.println("第二个定时任务开始 : " + LocalDateTime.now().toLocalTime() + "
    线程 : " + Thread.currentThread().getName());
            System.out.println();
        }
    }

    第一个定时任务和第二个定时任务互不影响;由于开启了多线程,第一个任务的执行时间也不受其本身执行时间的限制,所以需要注意可能会出现重复操作导致数据异常。

    三、分布式环境下的定时任务

    我们有很多时候都需要一些定时任务的辅助,大多数情况,定时任务都可能是写到一个服务节点。但是可能存在以下情况:

    • 如果业务逻辑过于复杂的话,不好维护。
    • 如果服务节点挂了,那么所有的定时任务都不会执行了。

    可能有人说部署多个节点不久可以了吗?确实,但是有没有想过,定时任务会重复执行,这怎么解决呢?使用redis分布式锁。

    查看本教程中的 “RedisTemplate详解” ,获取RedisUtil的代码。

    boolean flag = false;
    try {
        //1. 获取锁
        flag = RedisUtil.lock("smsSend", 1000); //key和过期时间,过期时间根据实际业务预测
        if(flag) {
            //2. 执行业务逻辑
        }else {
            logger.info("分布式锁没有获取到锁:{}");
        }
    } catch (Exception e) {
        logger.info("定时任务执行失败,分布式锁异常"+e);
    }finally {
        if(flag) {//释放锁
            RedisUtil.unlock("smsSend");
            logger.info("锁已释放");
        }
    }
  • 相关阅读:
    Study Plan The FortyEighth Day
    原码与补码
    【innoDB】加锁案例分析
    【InnoDB】事务基础知识
    了解 CAP
    妙用位运算
    Go学习笔记
    .NET Hot Reload热重载
    .NET 6 中的 dotnet monitor
    C# 实现多线程的同步方法详解
  • 原文地址:https://www.cnblogs.com/myitnews/p/11848571.html
Copyright © 2011-2022 走看看