• Spring Task单机定时任务(使用及阻塞问题解决)


    一、介绍

    SpringTask是Spring自主研发的定时任务工具,并且存在于Spring体系中,不需要添加任何依赖
    Spring Boot 默认在无任何第三方依赖的情况下使用 spring-context 模块下提供的定时任务工具 Spring Task。

    • 我们只需要使用 @EnableScheduling 注解就可以开启相关的定时任务功能
    @SpringBootApplication
    @EnableTransactionManagement //开启注解方式的事务管理
    @Slf4j
    @EnableCaching
    @EnableScheduling
    public class SkyApplication {
        public static void main(String[] args) {
            SpringApplication.run(SkyApplication.class, args);
            log.info("server started");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    二、SpringTask使用

    @Scheduled的常见用法,包括:固定速率执行、固定延迟执行、初始延迟执行、使用 Cron 表达式执行定时任务。

    Cron 表达式: 主要用于定时作业(定时任务)系统定义执行时间或执行频率的表达式,你可以通过 Cron 表达式进行设置定时任务每天或者每个月什么时候执行等等操作。
    推荐一个在线Cron表达式生成器:https://cron.qqe2.com/

    @Component
    public class ScheduledTasks {
        private static final Logger log = LoggerFactory.getLogger(ScheduledTasks.class);
        private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
    
        /**
         * fixedRate:固定速率执行。每5秒执行一次。
         */
        @Scheduled(fixedRate = 5000)
        public void reportCurrentTimeWithFixedRate() {
            log.info("Current Thread : {}", Thread.currentThread().getName());
            log.info("Fixed Rate Task : The time is now {}", dateFormat.format(new Date()));
        }
    
        /**
         * fixedDelay:固定延迟执行。距离上一次调用成功后2秒才执。
         */
        @Scheduled(fixedDelay = 2000)
        public void reportCurrentTimeWithFixedDelay() {
            try {
                TimeUnit.SECONDS.sleep(3);
                log.info("Fixed Delay Task : The time is now {}", dateFormat.format(new Date()));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * initialDelay:初始延迟。任务的第一次执行将延迟5秒,然后将以5秒的固定间隔执行。
         */
        @Scheduled(initialDelay = 5000, fixedRate = 5000)
        public void reportCurrentTimeWithInitialDelay() {
            log.info("Fixed Rate Task with Initial Delay : The time is now {}", dateFormat.format(new Date()));
        }
    
        /**
         * cron:使用Cron表达式。 每分钟的1,2秒运行
         */
        @Scheduled(cron = "1-2 * * * * ? ")
        public void reportCurrentTimeWithCronExpression() {
            log.info("Cron Expression: The time is now {}", dateFormat.format(new Date()));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    Cron

    cron表达式是一个字符串,字符串以5或6个空格隔开,分开共6或7个域,每一个域代表一个含义。

    cron 表达式语法:
    格式:[秒] [分] [小时] [日] [月] [周] [年]

    三、实例

    外卖项目中,使用springtask对订单设计定时任务:
    处理超时订单(超过15min 修改status)
    处理“派送中”订单(每日凌晨1点,status设为完成)

    /**
     * 自定义定时任务,实现订单状态定时处理
     */
    @Component
    @Slf4j
    public class OrderTask {
    
        @Autowired
        private OrderMapper orderMapper;
    
        /**
         * 处理支付超时订单
         */
        @Scheduled(cron = "0 * * * * ?")
        public void processTimeoutOrder(){
            log.info("处理支付超时订单:{}", new Date());
            ...
        }
    
        /**
         * 处理“派送中”状态的订单
         */
        @Scheduled(cron = "0 0 1 * * ?")
        public void processDeliveryOrder(){
            log.info("处理派送中订单:{}", new Date());
            ...
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    四、SpringTask阻塞问题

    Spring 的定时任务默认是单线程执行
    也就是说,如果任务执行时间超过定时任务间隔时间,不管是同一个定时任务还是不同的定时任务,下一个任务都会被阻塞。
    举个例子:

    @Component
    @Slf4j
    public class TaskTest {
        private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
        private List<Integer> index = Arrays.asList(6, 6, 2, 3);
    
        int i = 0;
        @Scheduled(fixedRate = 5000)//固定速率,每隔5秒一次
        public void reportCurrentTimeWithFixedRate() {
        	log.info("Current Thread : {}", Thread.currentThread().getName());
            if (i == 0) {
                log.info("Start time is {}", dateFormat.format(new Date()));
            }
            if (i < 5) {
                try {
                    TimeUnit.SECONDS.sleep(index.get(i));
                    log.info("Fixed Rate Task : The time is now {}", dateFormat.format(new Date()));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                i++;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    在这里插入图片描述
    在这里插入图片描述

    解决方法

    自定义线程池执行 scheduled task:
    默认情况下,@Scheduled任务都在Spring创建的大小为1的默认线程池中执行。上面可以看出来:scheduling-1

    1、实现SchedulingConfigurer接口

    实现SchedulingConfigurer接口的 configureTasks 的类即可,这个类需要加上 @Configuration 注解。

    
    @Configuration
    public class SchedulerConfig implements SchedulingConfigurer {
        private final int POOL_SIZE = 10;
    
        @Override
        public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
            ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
    
            threadPoolTaskScheduler.setPoolSize(POOL_SIZE);
            threadPoolTaskScheduler.setThreadNamePrefix("my-scheduled-task-pool-");
            threadPoolTaskScheduler.initialize();
    
            scheduledTaskRegistrar.setTaskScheduler(threadPoolTaskScheduler);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    实现SchedulingConfigurer接口后,定时任务会变成多线程执行。不同的定时任务之间互不影响,同一个定时任务(方法)依然会有被阻塞的机制。
    如果定时任务交给线程池处理,则下一个任务不会被阻塞。
    在这里插入图片描述

    二、加入线程池

    我们采用的是加入注解@Async@EnableAsync

    从Spring3开始提供了@Async注解,该注解可以被标注在方法上,以便异步地调用该方法。调用者将在调用时立即返回,方法的实际执行将提交给Spring TaskExecutor的任务中,由指定的线程池中的线程执行。

    @Component
    @EnableAsync
    @Slf4j
    public class TaskTest {
            private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
    
            /**
             * fixedDelay:固定延迟执行。距离上一次调用成功后2秒才执。
             */
            //@Async
            @Scheduled(fixedDelay = 2000)
            public void reportCurrentTimeWithFixedDelay() {
                log.info("Current Thread : {}", Thread.currentThread().getName());
                try {
                    TimeUnit.SECONDS.sleep(3);
                    log.info("Fixed Delay Task : The time is now {}", dateFormat.format(new Date()));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    固定延迟2秒 sleep3秒,也就是 5秒一次。
    未使用@Async
    在这里插入图片描述

    1、在使用的类上加@EnableAsync注解 开启异步
    2、方法上添加@Async注解
    之后 是 每2秒执行一次。
    在这里插入图片描述
    学习:https://github.com/Snailclimb/springboot-guide

  • 相关阅读:
    【解读】工信部数据安全能力提升实施方案
    QT 5.15向服务器发送POST请求,接收并解析服务器的响应数据(嵌套解析JSON数据)
    visummirrors seurattoscanpy dietseurat
    leetcode做题笔记201. 数字范围按位与
    TIM(4)Encoder_interface
    jenkins升级版本遇到的问题
    opencv重点知识
    java计算机毕业设计江西婺源旅游文化推广系统源码+mysql数据库+系统+lw文档+部署
    JavaWeb——IDEA操作:Project最终新建module
    电源常用LDO线性稳压IC大全!
  • 原文地址:https://blog.csdn.net/m0_60708917/article/details/134442993