Spring Boot: 定时任务与异步处理

最后更新:2026-08-26

定时任务和异步处理是后台效率的加速器——该定时的定时,该并行的并行,不浪费用户等待时间。

1. 你将学到


2. 一个业务开发者的真实故事

(1) 痛点:超时订单积压

Alice 发现 OrderFlow 中大量未支付订单堆积——用户下单后不付款,库存被锁定,其他用户无法购买。Charlie 报告说每天约有 2 thousand 个超时订单,累计锁定了价值 50 thousand USD 的库存。之前靠人工每 30 分钟查一次数据库取消订单,效率低下且容易遗漏。

(2) @Scheduled 的解法

Spring Boot 内置定时任务,一条注解即可实现:

JAVA
@Scheduled(fixedRate = 5 * 60 * 1000)  // Every 5 minutes
public void cancelExpiredOrders() {
    List<Order> expired = orderRepository.findByStatusAndCreatedAtBefore(
        "PENDING", Instant.now().minus(30, ChronoUnit.MINUTES));
    expired.forEach(order -> cancelOrder(order));
    log.info("Cancelled {} expired orders", expired.size());
}

(3) 收益

Alice 实现定时任务后,超时订单每 5 分钟自动清理,库存自动释放,超时积压从 2 thousand/天降到 0。同时用 @Async 将通知邮件改为异步发送,下单接口响应时间从 800ms 降到 50ms。


3. @Scheduled 定时任务

(1) 三种调度方式

方式 注解参数 含义 示例
FixedRate fixedRate 固定间隔(从上次开始计时) 每 5 分钟执行
FixedDelay fixedDelay 固定延迟(从上次结束计时) 上次结束后等 5 分钟
Cron cron Cron 表达式 每天凌晨 2 点执行
100%
gantt
    title FixedRate vs FixedDelay Timing
    dateFormat X
    axisFormat %s

    section FixedRate
    Task A (5min) :a1, 0, 5
    Task B (5min) :a2, 5, 8
    Task C (5min) :a3, 10, 14

    section FixedDelay
    Task A (5min) :b1, 0, 5
    wait 3min     :b2, 5, 8
    Task B (5min) :b3, 8, 13
    wait 3min     :b4, 13, 16
维度 FixedRate FixedDelay Cron
执行间隔 固定(不管上次耗时) 固定(等上次结束) 精确时间点
任务重叠 可能重叠 不会重叠 可能重叠
适用场景 轮询检查 数据处理 定时批处理

▶ 示例: FixedRate 定时任务

JAVA
@Component
@EnableScheduling
public class OrderScheduleTask {

    private final OrderService orderService;
    private static final Logger log = LoggerFactory.getLogger(OrderScheduleTask.class);

    public OrderScheduleTask(OrderService orderService) {
        this.orderService = orderService;
    }

    @Scheduled(fixedRate = 5 * 60 * 1000)  // Every 5 minutes
    public void cancelExpiredOrders() {
        log.info("Starting expired order cleanup...");
        int cancelled = orderService.cancelExpiredOrders(Duration.ofMinutes(30));
        log.info("Cancelled {} expired orders", cancelled);
    }
}

输出:

TEXT 📖 仅展示
// 执行成功

▶ 示例: Cron 表达式定时任务

JAVA
@Component
public class DailyReportTask {

    @Scheduled(cron = "0 0 2 * * ?")  // Every day at 2:00 AM
    public void generateDailyReport() {
        // Generate daily order statistics report
    }

    @Scheduled(cron = "0 0 0 1 * ?")  // First day of every month at midnight
    public void generateMonthlyReport() {
        // Generate monthly report
    }
}

输出:

TEXT 📖 仅展示
// 执行成功
Cron 字段 含义 取值范围
Seconds 0-59
Minutes 0-59
Hours 0-23
Day of month 1-31
Month 1-12
星期 Day of week 0-7 (0/7=Sun)
常用 Cron 含义
0 0 2 * * ? 每天凌晨 2 点
0 */5 * * * ? 每 5 分钟
0 0 0 1 * ? 每月 1 号零点
0 0 9-17 * * MON-FRI 工作日 9-17 点整点

4. @Async 异步处理

(1) 同步 vs 异步

▶ 示例: 同步发送通知(阻塞)

JAVA
@Service
public class OrderService {

    public Order createOrder(CreateOrderRequest request) {
        Order order = saveOrder(request);
        emailService.sendConfirmation(order);   // Blocks for 500ms
        smsService.sendNotification(order);       // Blocks for 200ms
        return order;  // Total: 700ms wasted
    }
}

输出:

TEXT 📖 仅展示
// 执行成功

▶ 示例: 异步发送通知(非阻塞)

JAVA
@Service
@EnableAsync
public class NotificationService {

    @Async
    public void sendOrderConfirmation(Order order) {
        // This runs in a separate thread
        // Does not block the caller
        emailService.sendConfirmation(order);
        log.info("Confirmation email sent for order {}", order.getId());
    }

    @Async
    public CompletableFuture<String> sendSmsNotification(Order order) {
        smsService.sendNotification(order);
        return CompletableFuture.completedFuture("SMS sent");
    }
}

// In OrderService
@Service
public class OrderService {

    private final NotificationService notificationService;

    public Order createOrder(CreateOrderRequest request) {
        Order order = saveOrder(request);
        notificationService.sendOrderConfirmation(order);  // Non-blocking
        return order;  // Returns immediately
    }
}

输出:

TEXT 📖 仅展示
// 执行成功
维度 同步 异步
调用方式 阻塞等待结果 立即返回,后台执行
响应时间 累加所有操作耗时 仅核心业务耗时
错误处理 try-catch 直接捕获 AsyncUncaughtExceptionHandler
适用场景 必须等待结果的操作 通知、日志、非关键操作

5. 自定义线程池

(1) 默认线程池的问题

Spring Boot 默认使用 SimpleAsyncTaskExecutor,它为每个任务创建新线程,不限制线程数,可能导致 OOM。

▶ 示例: 自定义 ThreadPoolTaskExecutor

JAVA
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {

    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(20);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("orderflow-async-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.setWaitForTasksToCompleteOnShutdown(true);
        executor.setAwaitTerminationSeconds(60);
        executor.initialize();
        return executor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return (ex, method, params) -> {
            log.error("Async exception in method {}: {}", method.getName(), ex.getMessage(), ex);
        };
    }
}

输出:

TEXT 📖 仅展示
// 执行成功
参数 含义 推荐值
corePoolSize 核心线程数(常驻) CPU 核数
maxPoolSize 最大线程数 CPU 核数 × 2
queueCapacity 队列容量 100-1000
threadNamePrefix 线程名前缀 项目名-async-
rejectedExecutionHandler 拒绝策略 CallerRunsPolicy
拒绝策略 行为 适用场景
AbortPolicy 抛 RejectedExecutionException 默认,快速失败
CallerRunsPolicy 调用者线程执行 降级但不丢弃
DiscardPolicy 静默丢弃 可容忍丢失
DiscardOldestPolicy 丢弃最老任务 实时性优先

6. 异步异常处理

▶ 示例: AsyncUncaughtExceptionHandler

JAVA
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {

    private static final Logger log = LoggerFactory.getLogger(AsyncConfig.class);

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return (throwable, method, objects) -> {
            log.error("Async method [{}] threw exception: {}",
                method.getName(), throwable.getMessage(), throwable);
            // Could also publish to monitoring system
        };
    }
}

输出:

TEXT 📖 仅展示
// 执行成功
📌 重点: @Async void 方法的异常不会传播到调用者,必须通过 AsyncUncaughtExceptionHandler 处理。返回 CompletableFuture 的方法可以通过 future.exceptionally() 处理。


7. 综合示例:OrderFlow 超时取消 + 异步通知

JAVA
// OrderScheduleTask.java
package com.orderflow.schedule;

import com.orderflow.service.OrderService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.Duration;

@Component
public class OrderScheduleTask {

    private static final Logger log = LoggerFactory.getLogger(OrderScheduleTask.class);
    private final OrderService orderService;

    public OrderScheduleTask(OrderService orderService) {
        this.orderService = orderService;
    }

    @Scheduled(fixedRateString = "${orderflow.order.expiry-check-interval:300000}")
    public void cancelExpiredOrders() {
        int timeoutMinutes = 30;
        int cancelled = orderService.cancelExpiredOrders(Duration.ofMinutes(timeoutMinutes));
        if (cancelled > 0) {
            log.info("Cancelled {} orders expired for {} minutes", cancelled, timeoutMinutes);
        }
    }

    @Scheduled(cron = "0 0 2 * * ?")
    public void generateDailyReport() {
        log.info("Generating daily order report...");
        orderService.generateDailyReport();
    }
}

// NotificationService.java
package com.orderflow.service;

import com.orderflow.model.Order;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.util.concurrent.CompletableFuture;

@Service
public class NotificationService {

    private static final Logger log = LoggerFactory.getLogger(NotificationService.class);

    @Async
    public void sendOrderConfirmation(Order order) {
        // Simulate email sending
        log.info("Sending confirmation email for order {}", order.getId());
    }

    @Async
    public void sendCancellationNotice(Order order) {
        log.info("Sending cancellation notice for order {}", order.getId());
    }

    @Async
    public CompletableFuture<String> sendSmsNotification(Order order) {
        log.info("Sending SMS for order {}", order.getId());
        return CompletableFuture.completedFuture("SMS sent for order " + order.getId());
    }
}

// AsyncConfig.java
@Configuration
@EnableAsync
@EnableScheduling
public class AsyncConfig implements AsyncConfigurer {
    private static final Logger log = LoggerFactory.getLogger(AsyncConfig.class);

    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor exec = new ThreadPoolTaskExecutor();
        exec.setCorePoolSize(5);
        exec.setMaxPoolSize(20);
        exec.setQueueCapacity(100);
        exec.setThreadNamePrefix("orderflow-async-");
        exec.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        exec.setWaitForTasksToCompleteOnShutdown(true);
        exec.setAwaitTerminationSeconds(60);
        exec.initialize();
        return exec;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return (ex, method, params) ->
            log.error("Async error in {}: {}", method.getName(), ex.getMessage(), ex);
    }
}

❓ 常见问题

Q @Scheduled 只能用单线程执行吗?
A 默认是的。多个 @Scheduled 方法共用一个单线程调度器,长任务会阻塞其他任务。解决方案:配置 TaskScheduler 线程池,或把耗时操作用 @Async 委托给线程池。
Q @Async 在同一个类内部调用会生效吗?
A 不会。和 @Transactional 一样,@Async 基于 AOP 代理,同类方法互调不走代理。解决方案:1)拆分到不同 Service;2)注入自身代理。
Q fixedRate 和 fixedDelay 在长任务时有什么区别?
A fixedRate 从上次开始时间计时,如果任务耗时超过间隔,任务会连续执行甚至重叠。fixedDelay 从上次结束时间计时,保证任务间有固定间隔,不会重叠。
Q 生产环境定时任务如何避免多实例重复执行?
A 1)数据库行锁(SELECT FOR UPDATE);2)分布式锁(Redis/ShedLock);3)只在一个实例上启用调度。推荐 ShedLock 库,专门解决此问题。
Q 如何动态修改定时任务的时间?
A 可以使用 ScheduledTaskRegistrar 编程式注册任务,或配合配置中心动态刷新。简单方案:fixedRateString = "${interval}" 通过外部配置控制。
Q 异步方法返回 CompletableFuture 和 void 有什么区别?
A void 方法"fire-and-forget",调用者不关心结果,异常由 AsyncUncaughtExceptionHandler 处理。CompletableFuture 方法可以链式处理结果和异常,调用者有更多控制权。

📖 小节


📝 作业

  1. 基础题(难度⭐):为 OrderFlow 实现超时订单自动取消定时任务,每 5 分钟检查并取消 30 分钟未支付的 PENDING 订单。

  2. 进阶题(难度⭐⭐):将订单确认邮件和 SMS 通知改为 @Async 异步发送,自定义线程池(core=5, max=20),验证下单接口响应时间显著降低。

  3. 挑战题(难度⭐⭐⭐):集成 ShedLock 实现分布式定时任务锁,确保 OrderFlow 多实例部署时同一时刻只有一个实例执行定时任务,思考锁的粒度和超时设计。

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏