0


IDEA SpringBoot实现定时任务(保姆级教程,超详细!!!)

1. 前言

在现代化应用中,定时任务(Scheduled Tasks)是不可或缺的一部分,它们允许开发者在预定的时间间隔内自动执行特定任务,如数据同步、报告生成、系统维护等。Spring Boot作为一款流行的Java开发框架,提供了简洁而强大的定时任务实现方式。

Spring Boot通过内置的@Scheduled注解和@EnableScheduling配置,使得开发者能够轻松地在应用中集成定时任务功能。无需引入额外的依赖或复杂的配置,只需在方法上添加@Scheduled注解,并指定任务的执行计划,Spring Boot便会自动处理任务的调度和执行。

本教程将带您逐步了解Spring Boot定时任务的实现过程,包括基本的配置和注解使用、常见的执行计划设置、以及高级功能如动态定时任务、多线程定时任务等。通过本教程的学习,您将能够掌握在Spring Boot中高效实现定时任务的方法和技巧,为您的应用增添更多自动化和智能化的功能。

请注意,随着技术的不断发展,Spring Boot的版本和特性也在不断更新。因此,建议参考最新的Spring Boot官方文档和相关教程来获取最准确的信息。同时,本教程也提供了实用的示例和代码,帮助您更好地理解和应用定时任务功能。

2. 创建SpringBoot项目

首先,我们需要创建SpringBoot项目。在创建SpringBoot项目时,可以选择使用Spring Initializr来快速生成项目结构。

创建SpringBoot项目教程,本文就不过多讲解了,具体操作可参考往期博文:

IDEA创建SpringBoot项目教程,讲解超详细(2024最新)!!!

3. Maven依赖引入

在Spring Boot项目中使用@Scheduled注解实现定时任务时,你通常不需要额外导入特定的依赖,因为@Scheduled是Spring框架的核心功能之一,并且Spring Boot会自动配置与调度相关的组件。

但是,确保你的Spring Boot项目包含了Spring Boot的起步依赖(starter dependencies),特别是spring-boot-starter或与你项目相关的特定starter依赖(比如spring-boot-starter-web用于Web应用),这些starter依赖会包含使用@Scheduled所需的所有必要组件。

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

4. 创建定时任务

@Slf4j
@Component
@EnableScheduling
public class DemoTask {

    // 每10秒执行一次,cron的表达式暂时不多说明了,可以自行百度学习
    @Scheduled(cron = "0/10 * * * * ? ")
    public void testSchedule() {
        log.info("当前执行任务的线程号ID===>{}", Thread.currentThread().getId()); // 日志输出
    }

}

注释讲解

** @Slf4j:**Lombok库提供的一个注解,用于在Java类中自动生成一个名为log的日志对象。通过使用@Slf4j注解,你可以直接在代码中调用log对象的方法来记录不同级别的日志,如log.info(), log.debug(), log.error() 等。这些日志方法可以帮助你记录应用程序的运行状态、调试信息、警告和错误等,从而有助于排查问题、跟踪程序运行情况,提高系统稳定性和可维护性。

@Component:Spring框架中的一个核心注解,它用于将一个类标记为Spring上下文中的一个组件。当一个类被标记为@Component时,Spring容器会在启动时自动扫描并实例化这个类,并将其注册到Spring上下文中。这个类就成为了Spring上下文中的一个bean,可以被其他bean通过依赖注入的方式使用。

@EnableScheduling:Spring框架提供的一个注解,用于启用基于注解的定时任务调度功能。当在Spring的配置类(如使用@Configuration注解的类)上使用@EnableScheduling注解时,Spring会自动配置一个任务调度器(TaskScheduler),负责管理所有带有@Scheduled注解的方法。

@Scheduled:Spring框架中用于定时任务调度的注解。它允许开发者将一个方法标记为定时任务,并配置任务的执行时间间隔或Cron表达式,从而实现在指定时间或按照指定周期自动执行该方法的功能。 除了配置Cron表达式外,还可以通过fixedRate和fixedDelay两种方式设置定时任务,这两种方式可以自行了解。

执行结果

按照上面代码中给定的cron表达式@Scheduled(cron = "0/10 * * * * ? ")每十秒执行一次,那么最近几次的执行结果应当为:

5. 问题:执行时间延迟和单线程执行

如果定时任务中是执行非常快的任务的,时间非常非常短,按照上述方法实现定时任务,确实不会有什么的延迟性,但真实的业务场景中,业务的执行时间可能远比这里时间长。

例如:主动让线程睡上20秒,让我们再来看看输出结果是如何的吧。

@Slf4j
@Component
@EnableScheduling
public class DemoTask {

    // 每10秒执行一次,cron的表达式暂时不多说明了,可以自行百度学习
    @Scheduled(cron = "0/10 * * * * ? ")
    public void testSchedule() {
        try {
            Thread.sleep(20000); // 休眠20秒,模拟业务场景执行时间
            log.info("当前执行任务的线程号ID===>{}", Thread.currentThread().getId()); // 日志输出
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

执行结果

  • 执行时间延迟:从时间上可以明显看出,不再是每10秒执行一次,执行时间延迟很多,造成任务的。
  • 单线程执行:从始至终都只有一个线程在执行任务,造成任务的堵塞。

5.1 问题原因

  • 任务阻塞:定时任务设置了每10秒执行一次,但任务实际执行了20秒,那么下一个任务的执行就会因为前一个任务尚未完成而被阻塞。
  • 单线程调度器:当使用@EnableScheduling默认配置创建定时任务时,通常Spring会使用一个单线程的TaskScheduler来执行这些定时任务。在默认配置下,这个单线程的调度器会顺序地执行每一个任务,而不会并行处理。

归根到底,线程阻塞式执行,执行任务线程数量过少 ~

5.2 解决方式

方式一:执行逻辑改为异步

首先我们先配置一个线程池,参数自己设置即可,我这里比较随意。

@Configuration
public class DemoTheadPoolConfig {
    @Bean(name = "TaskExecutor")
    public TaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //设置核心线程数
        executor.setCorePoolSize(10);
        //设置最大线程数
        executor.setMaxPoolSize(20);
        //缓冲队列200:用来缓冲执行任务的队列
        executor.setQueueCapacity(200);
        //线程活路时间 60 秒
        executor.setKeepAliveSeconds(60);
        //线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池
        executor.setThreadNamePrefix("demo-thread-");
        //设置拒绝策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.setWaitForTasksToCompleteOnShutdown(true);
        return executor;
    }
}

然后在定时任务中的将模拟业务的休眠20秒,改造成多线程并发的方式执行。

    @Scheduled(cron = "0/10 * * * * ? ")
    public void testSchedule() {
        CompletableFuture.runAsync(() -> {
            try {
                Thread.sleep(20000); // 休眠20秒,模拟业务场景执行时间
                log.info("当前执行任务的线程号ID===>{}", Thread.currentThread().getId()); // 日志输出
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, taskExecutor);
    }

执行结果

可以看到虽然业务执行时间仍然是20秒,但是10秒的定时任务没有再出现延迟执行的情况。

方式二:异步定时任务

异步定时任务其实和上面的方式原理是一样的,不过实现稍稍不同罢了,在定时任务的类上再加一个@EnableAsync注解,给方法添加一个@Async即可。

注意:

  • @EnableAsync:开启异步任务
  • @Async:给希望异步执行的方法标注
  • 一般使用@Async都会指定线程池,比如写成这样@Async(value = "TaskExecutor")

@Slf4j
@Component
@EnableAsync
@EnableScheduling
public class DemoTask {

    // 每10秒执行一次,cron的表达式暂时不多说明了,可以自行百度学习
    @Async("TaskExecutor")
    @Scheduled(cron = "0/10 * * * * ? ")
    public void testSchedule() {
        try {
            Thread.sleep(20000); // 休眠20秒,模拟业务场景执行时间
            log.info("当前执行任务的线程号ID===>{}", Thread.currentThread().getId()); // 日志输出
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

执行结果

结果显而易见也是和第一种方式一样的。


至此,我们的SpringBoot实现定时任务项目完美竣工!!!

ps:本教程我所阐述的这种方式,只能说适用于简单的单体项目,实际业务比这要复杂的多,如分布式场景还需考虑定时任务在多个机器下运行的问题,后续作者也会针对该问题出一篇分布式锁的教程,如感兴趣关注点一下吧!!!

有什么问题都可以评论区留言,看见都会回复的!!!

如果你觉得本篇文章对你有所帮助的,多多支持!!!

点赞收藏评论,抱拳了!!!


本文转载自: https://blog.csdn.net/weixin_47698656/article/details/140448123
版权归原作者 Michael Lee. 所有, 如有侵权,请联系我们删除。

“IDEA SpringBoot实现定时任务(保姆级教程,超详细!!!)”的评论:

还没有评论