SpringBoot的@Scheduled和@Schedules的用法及区别介绍

 更新时间:2026年05月08日 15:17:14   作者:qwert1037  
文章详细解析了Spring框架中`@Scheduled`注解的用法,包括Cron表达式、fixedRate、fixedDelay、initialDelay等参数的使用方法及场景选择,还介绍了如何启用定时任务、自定义TaskScheduler、错误处理、最佳实践及常见问题解决方法,并通过案例展示了如何配置定时任务

@Scheduled 的详细解析

参数详解

cron:使用Cron表达式来指定复杂的调度模式。

Cron表达式的格式如下:

  • 秒(0-59)
  • 分钟(0-59)
  • 小时(0-23)
  • 日(1-31)
  • 月(1-12 或 JAN-DEC)
  • 星期(0-7 或 SUN-SAT,其中0和7都表示星期日)
  • 年(可选,1970-2099)

Cron表达式的每个字段可以是具体的值、范围、列表或通配符(*)。

例如:

  • "0 0 12 * * ?"表示每天中午12点。
  • "0 15 10 ? * MON-FRI"表示周一至周五上午10:15执行。
  • "0 0/5 * * * ?"表示每5分钟执行一次。
  • "0 0 12 1 * ?"表示每月第一天中午12点执行。

fixedRate:指定以固定的速率重复执行任务,从前一次任务开始时刻算起。它不会等待前一个任务完成,因此如果任务执行时间超过了设定的时间间隔,可能会有重叠的任务实例在运行。

fixedDelay:类似于 fixedRate,但是它是以前一次任务的完成时刻作为下一次任务启动的时间基准。这种方式可以确保每次只有一个任务实例在运行,前提是任务的执行时间短于延迟时间。

initialDelay:在第一次执行之前等待的时间(毫秒)。这个参数通常与 fixedRatefixedDelay 一起使用,用来设置首次执行前的延迟。

zone:定义时区,默认是系统的默认时区。如果你的应用需要在全球不同地区运行,明确指定时区可能是很重要的。

Cron表达式、fixedRate、fixedDelay、initialDelay如何选择

  • 如果你需要非常具体的调度模式,如每天凌晨两点执行某个任务,那么应该选择Cron表达式。
  • 如果你希望任务以固定的速率重复执行,不论每次执行花费多少时间,你应该选择fixedRate
  • 如果你希望在前一个任务完全结束后再等待一段固定时间才开始下一个任务,那么fixedDelay是更好的选择。
  • 如果你需要设置首次执行的延迟,可以添加initialDelay参数到你的调度配置中。

示例代码:

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class ScheduledTasks {

    // 每天中午12点执行(上海时区)
    @Scheduled(cron = "0 0 12 * * ?", zone = "Asia/Shanghai")
    public void scheduledTaskUsingCron() {
        System.out.println("Scheduled task using cron at Asia/Shanghai timezone.");
    }

    // 每5秒执行一次,首次执行前等待2秒
    @Scheduled(fixedRate = 5000, initialDelay = 2000)
    public void scheduledTaskWithFixedRate() {
        System.out.println("Scheduled task with fixed rate.");
    }

    // 上次任务完成后等待3秒再执行下一次
    @Scheduled(fixedDelay = 3000)
    public void scheduledTaskWithFixedDelay() {
        System.out.println("Scheduled task with fixed delay.");
    }
}

@Schedules 的详细解析

@Schedules 允许多个 @Scheduled 注解组合在一起,为同一个方法设定多种不同的调度策略。

这对于那些需要在多个不同时间点或条件下触发的方法非常有用。

示例代码:

import org.springframework.scheduling.annotation.Schedules;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class MultipleScheduledTasks {

    // 每天中午12点执行,并且每5秒也执行一次
    @Schedules({
        @Scheduled(cron = "0 0 12 * * ?"),
        @Scheduled(fixedRate = 5000)
    })
    public void multipleScheduledTasks() {
        System.out.println("Multiple scheduled tasks.");
    }
}

启用和管理定时任务

要使这些注解生效,你需要确保你的Spring应用已经启用了对它们的支持。

这可以通过在配置类上添加 @EnableScheduling 来实现:

import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;

@Configuration
@EnableScheduling
public class SchedulingConfig {
    // 配置类内容
}

自定义 TaskScheduler

对于更复杂的需求,比如调整线程池大小或者设置线程名称前缀等,你可以通过自定义 TaskScheduler 来进行配置。

Spring提供了几种内置的调度器实现,如 ThreadPoolTaskSchedulerConcurrentTaskScheduler

示例代码:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;

@Configuration
public class SchedulerConfig {

    @Bean
    public ThreadPoolTaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
        taskScheduler.setPoolSize(10); // 设置线程池大小
        taskScheduler.setThreadNamePrefix("MyScheduledTask-");
        taskScheduler.setErrorHandler(t -> {
            System.err.println("Error occurred in scheduled task: " + t.getMessage());
        });
        taskScheduler.setWaitForTasksToCompleteOnShutdown(true);
        taskScheduler.setAwaitTerminationSeconds(60);
        return taskScheduler;
    }
}

错误处理

当一个预定任务抛出异常时,默认情况下Spring会记录错误日志,但任务本身不会被取消。

如果你想改变这种行为,可以使用 DelegatingErrorHandlingRunnable 或者直接在 ThreadPoolTaskScheduler 中设置错误处理器(如上面的示例所示)。

自定义错误处理逻辑

你可以创建自己的错误处理器来捕获并处理异常:

import org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;

@Configuration
public class AsyncConfig implements AsyncConfigurer {

    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(2);
        executor.setMaxPoolSize(5);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("Async-");
        executor.initialize();
        return executor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new SimpleAsyncUncaughtExceptionHandler() {
            @Override
            public void handleUncaughtException(Throwable ex, Method method, Object... params) {
                // 自定义异常处理逻辑
                System.err.println("Exception in async task: " + ex.getMessage());
            }
        };
    }
}

最佳实践

  • 避免长时间运行的任务:尽量不要让预定任务执行过长的时间,因为它们可能会阻塞其他任务的执行。如果任务有可能运行很长时间,请考虑将其拆分为更小的部分,或者使用异步处理。
  • 任务冲突管理:当使用 fixedRate 时,要注意任务可能会重叠。如果任务执行时间可能超过间隔时间,应该选择 fixedDelay 来避免这种情况。
  • 资源清理:确保在任务结束时正确释放任何获取的资源,比如数据库连接或文件句柄。
  • 监控和报警:建立适当的监控和报警机制,以便在任务失败时能够及时收到通知并采取行动。可以利用Spring Boot Actuator提供的健康检查端点,或者集成第三方监控工具如Prometheus、Grafana等。
  • 幂等性设计:确保任务逻辑具有幂等性,即多次执行相同的任务不会导致不一致的结果。这在分布式环境中尤为重要。
  • 日志记录:为每个任务添加详细的日志记录,包括任务开始时间和结束时间,以便追踪任务执行情况。
  • 测试:编写单元测试和集成测试来验证定时任务的行为是否符合预期。可以使用Mockito或其他测试框架模拟依赖服务。
  • 多实例部署的问题:在多实例部署的情况下,所有的实例都会尝试执行相同的定时任务,这可能导致数据竞争或重复执行。一种解决方案是使用分布式锁,如Redisson提供的RedLock,来保证同一时间只有一个实例执行特定的任务。
  • 性能优化:对于高并发场景下的定时任务,应该评估线程池的大小和任务的执行频率,避免因过多的任务同时启动而导致资源耗尽。可以通过限流、队列管理和异步处理等方式提高系统的稳定性和响应速度。

处理定时任务中的常见问题

  • 任务未按预期执行:检查日志以确定是否有任何异常或错误信息。确保任务方法是非静态的,并且没有被final修饰。确认 @EnableScheduling 已经正确启用。另外,检查是否存在其他因素阻止任务执行,如网络延迟或依赖服务不可用。
  • 任务执行顺序混乱:如果你有多个任务几乎同时执行,可能会出现执行顺序混乱的情况。确保你理解了 fixedRatefixedDelay 的区别,并根据需要选择合适的方式。此外,可以通过增加任务之间的最小间隔时间来减少冲突的可能性。
  • 多实例部署的问题:在多实例部署的情况下,所有实例都会尝试执行相同的定时任务。为了解决这个问题,可以引入分布式锁机制,如基于Redis的锁或Zookeeper的临时节点,以确保同一时间只有一个实例执行任务。
  • 长时间运行的任务:尽量不要让预定任务执行过长的时间,因为它们可能会阻塞其他任务的执行。如果任务有可能运行很长时间,请考虑将其拆分为更小的部分,或者使用异步处理,如通过消息队列(MQ)来分发任务。
  • 时区问题:确保你的应用程序正确处理时区差异,特别是在全球范围内运行时。可以在 @Scheduled 注解中显式指定 zone 参数,或者在整个应用程序中统一配置默认时区。

案例分析

假设你正在开发一个电子商务平台,需要每天凌晨2点生成前一天的销售报告。

你可以使用 @Scheduled 注解来安排这个任务:

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

@Service
public class DailyReportService {

    @Scheduled(cron = "0 0 2 * * ?", zone = "Asia/Shanghai")
    public void generateDailySalesReport() {
        // 执行生成销售报告的逻辑
        System.out.println("Generating daily sales report at Asia/Shanghai timezone.");
    }
}

此外,你还可以结合上述的最佳实践来增强任务的可靠性,例如:

  • 确保任务具有幂等性,即使由于某种原因任务重复执行也不会影响结果。
  • 添加详细的日志记录,帮助追踪任务的执行情况。
  • 实现错误处理逻辑,确保即使发生异常也能得到妥善处理。
  • 如果平台有多实例部署,考虑使用分布式锁来防止多个实例同时生成报告。

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • Java使用正则表达式匹配获取链接地址的方法示例

    Java使用正则表达式匹配获取链接地址的方法示例

    这篇文章主要介绍了Java使用正则表达式匹配获取链接地址的方法,简单分析了java正则匹配常用方法及获取网址链接的相关操作技巧,需要的朋友可以参考下
    2017-08-08
  • 详解Java的Proxy动态代理机制

    详解Java的Proxy动态代理机制

    Java有两种代理方式,一种是静态代理,另一种是动态代理。对于静态代理,其实就是通过依赖注入,对对象进行封装,不让外部知道实现的细节。很多 API 就是通过这种形式来封装的
    2021-06-06
  • Java 详解异常的处理机制

    Java 详解异常的处理机制

    异常是程序中的一些错误,但并不是所有的错误都是异常,并且错误有时候是可以避免的。比如你的代码少一个分号,那运行出来结果是提示是错误 java.lang.Error;如果你用System.out.println(11/0),那你是因为用0做了除数,会抛出 java.lang.ArithmeticException 的异常
    2021-11-11
  • java实现excel导出合并单元格的步骤详解

    java实现excel导出合并单元格的步骤详解

    这篇文章主要介绍了java实现excel导出合并单元格,通过使用Apache POI库,我们可以方便地创建Excel文件、填充数据、合并单元格和导出Excel文件,需要的朋友可以参考下
    2023-04-04
  • Java中try catch处理异常示例

    Java中try catch处理异常示例

    这篇文章主要给大家介绍了关于Java中try catch 的基本用法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-12-12
  • Java规则引擎Easy Rules的使用介绍

    Java规则引擎Easy Rules的使用介绍

    这篇文章主要介绍了Java规则引擎Easy Rules的使用介绍,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-06-06
  • 详解Java中多进程编程的实现

    详解Java中多进程编程的实现

    这篇文章主要介绍了详解Java中多进程编程的实现,和多线程一样,多进程同样是实现并发的一种方式,需要的朋友可以参考下
    2015-11-11
  • SpringBoot使用RestTemplate如何通过http请求将文件下载到本地

    SpringBoot使用RestTemplate如何通过http请求将文件下载到本地

    文章介绍了如何通过编写代码批量下载文件,解决了没有提供批量下载接口的问题,首先筛选出需要下载的文件ID,然后通过后端代码发起HTTP请求,将下载的资源写入本地文件中,总结了实现方式和注意事项,希望能为类似任务提供参考
    2025-02-02
  • SpringBoot整合Redis实现token缓存

    SpringBoot整合Redis实现token缓存

    于token通常会被多次使用,我们需要把它保存到缓存中,以减少频繁地访问数据库,本文主要介绍了SpringBoot整合Redis实现token缓存,感兴趣的可以了解一下
    2024-02-02
  • java10下编译lombok注解代码分享

    java10下编译lombok注解代码分享

    这篇文章给大家分享了java10下编译lombok注解的代码,有兴趣的朋友可以测试以下,学习参考下吧。
    2018-04-04

最新评论