Spring Boot 4 定时任务与异步线程池:从 @Scheduled 到高并发任务编排(生产级实战)

Spring Boot 4 后台任务(定时 + 异步)是系统的“隐形发动机”。本文将系统讲解 @Scheduled 定时任务、@Async 异步线程池、CompletableFuture 并行编排、任务异常处理,并给出分布式环境下防重复执行的实战方案,帮你写出既准又稳的后台任务。


一、为什么需要定时任务与异步线程池?

1️⃣ 典型后台任务场景

场景

例子

定时任务

每日报表、凌晨对账、缓存预热、日志清理

异步任务

发送短信 / 邮件、导出大文件、调用第三方接口

并行任务

批量查询多个接口后聚合结果


2️⃣ 不用线程池的代价

// ❌ 错误示例:每次 new Thread
new Thread(() -> doSomething()).start();

问题:

  • 线程创建销毁开销大
  • 无法控制并发数
  • 容易耗尽系统资源
  • 线程无法复用

正确姿势:统一交给线程池管理


二、Spring Boot 4 定时任务(@Scheduled)

1️⃣ 开启定时任务

@SpringBootApplication
@EnableScheduling
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

2️⃣ 基础用法(三种方式)

@Component
public class ScheduledTask {

    // 1️⃣ 固定频率(每 5 秒)
    @Scheduled(fixedRate = 5000)
    public void fixedRateTask() {
        System.out.println("fixedRate: " + LocalDateTime.now());
    }

    // 2️⃣ 固定延迟(上次结束 → 延迟 3 秒 → 下次开始)
    @Scheduled(fixedDelay = 3000)
    public void fixedDelayTask() {
        System.out.println("fixedDelay: " + LocalDateTime.now());
    }

    // 3️⃣ 初始延迟 + 固定频率
    @Scheduled(initialDelay = 10000, fixedRate = 5000)
    public void initialDelayTask() {
        System.out.println("initialDelay + fixedRate");
    }
}

📌 fixedRate vs fixedDelay 区别:

fixedRate:  |---5s---|---5s---|---5s---|
             ↑开始    ↑开始    ↑开始

fixedDelay: |---3s---| 延迟3s |---3s---|
             ↑结束    ↑开始    ↑结束

3️⃣ Cron 表达式(最常用 ⭐)

@Scheduled(cron = "0 0 2 * * ?") // 每天凌晨 2 点
public void dailyReport() {
    System.out.println("生成每日报表");
}
Cron 格式(6 位,Spring 支持 7 位)
秒 分 时 日 月 星期 [年]

字段

允许值

允许通配

0–59

, - * /

0–59

, - * /

0–23

, - * /

1–31

, - * ? / L W

1–12

, - * /

星期

1–7(1=周日)

, - * ? / L #

常用 Cron 示例

需求

表达式

每 5 秒

*/5 * * * * ?

每分钟

0 * * * * ?

每小时

0 0 * * * ?

每天凌晨 2 点

0 0 2 * * ?

每周一 3 点

0 0 3 ? * MON

每月 1 号

0 0 0 1 * ?

工作日 9 点

0 0 9 ? * MON-FRI

📌 在线 Cron 生成器推荐:cron.qqe2.com


4️⃣ 并行执行定时任务(关键!)

默认情况下,所有 @Scheduled 任务在同一个线程串行执行

// ❌ 一个任务阻塞,其他任务全部卡住
@Scheduled(fixedRate = 5000)
public void taskA() throws InterruptedException {
    Thread.sleep(10000); // 阻塞 10 秒
}

@Scheduled(fixedRate = 5000)
public void taskB() {
    System.out.println("taskB"); // 会被 taskA 阻塞
}

解决方案:配置定时任务线程池

@Configuration
public class SchedulingConfig {

    @Bean
    public TaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setPoolSize(10); // 10 个线程
        scheduler.setThreadNamePrefix("scheduled-");
        scheduler.setAwaitTerminationSeconds(60);
        scheduler.setWaitForTasksToCompleteOnShutdown(true);
        scheduler.initialize();
        return scheduler;
    }
}

效果:多个定时任务并行执行,互不干扰。


三、Spring Boot 4 异步线程池(@Async)

1️⃣ 开启异步支持

@SpringBootApplication
@EnableAsync
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

2️⃣ 基础用法

@Service
public class EmailService {

    @Async
    public CompletableFuture<Void> sendEmail(String to, String content) {
        Thread.sleep(3000); // 模拟耗时
        System.out.println("邮件发送完成: " + to);
        return CompletableFuture.completedFuture(null);
    }
}
@RestController
@RequestMapping("/test")
public class TestController {

    @Autowired
    private EmailService emailService;

    @GetMapping("/email")
    public Result<?> send() {
        emailService.sendEmail("test@test.com", "Hello");
        return Result.success("邮件发送中...");
    }
}

📌 效果:接口立即返回,邮件在后台异步发送。


3️⃣ 自定义异步线程池(生产必配 ⭐)

默认线程池:

  • 核心线程数:8
  • 队列:无界(可能 OOM)

自定义线程池:

@Configuration
@EnableAsync
public class AsyncConfig {

    @Bean("taskExecutor")
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);      // 核心线程数
        executor.setMaxPoolSize(50);       // 最大线程数
        executor.setQueueCapacity(200);    // 队列容量
        executor.setKeepAliveSeconds(60);  // 空闲线程存活时间
        executor.setThreadNamePrefix("async-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.setWaitForTasksToCompleteOnShutdown(true);
        executor.setAwaitTerminationSeconds(30);
        executor.initialize();
        return executor;
    }
}

指定使用线程池:

@Async("taskExecutor")
public CompletableFuture<Void> sendEmail(...) { ... }

📌 拒绝策略对比:

策略

行为

AbortPolicy(默认)

抛异常

CallerRunsPolicy

由调用线程执行(推荐)

DiscardPolicy

直接丢弃

DiscardOldestPolicy

丢弃队列最老任务


4️⃣ 异步任务返回值

@Async
public CompletableFuture<Integer> calculate() {
    Thread.sleep(2000);
    return CompletableFuture.completedFuture(100);
}

// 调用
CompletableFuture<Integer> future = calculate();
Integer result = future.get(); // 阻塞获取

四、CompletableFuture 并行编排(高阶)

1️⃣ 并行执行多个任务

@Service
public class AggregateService {

    @Async("taskExecutor")
    public CompletableFuture<User> getUser() { ... }

    @Async("taskExecutor")
    public CompletableFuture<Order> getOrder() { ... }

    @Async("taskExecutor")
    public CompletableFuture<Score> getScore() { ... }

    public Result<?> aggregate() throws Exception {
        CompletableFuture<User> userFuture = getUser();
        CompletableFuture<Order> orderFuture = getOrder();
        CompletableFuture<Score> scoreFuture = getScore();

        // 等待所有完成
        CompletableFuture.allOf(userFuture, orderFuture, scoreFuture).join();

        User user = userFuture.get();
        Order order = orderFuture.get();
        Score score = scoreFuture.get();

        return Result.success(Map.of(
                "user", user,
                "order", order,
                "score", score
        ));
    }
}

3 个任务并行执行,总耗时 ≈ 最慢的那个,而不是累加。


2️⃣ 异常处理

CompletableFuture.supplyAsync(() -> {
    throw new RuntimeException("error");
}).exceptionally(ex -> {
    System.out.println("异常: " + ex.getMessage());
    return null;
});

五、定时任务 + 异步:黄金组合

1️⃣ 定时触发,异步执行

@Component
public class DailyReportTask {

    @Autowired
    private ReportService reportService;

    @Scheduled(cron = "0 0 2 * * ?")
    @Async("taskExecutor")
    public void generateReport() {
        reportService.generate(); // 耗时任务异步执行
    }
}

好处:

  • 定时任务线程不阻塞
  • 可并行执行多个定时任务
  • 系统资源利用率高

六、分布式环境下的定时任务(防重复执行)

❌ 问题:多实例部署时,每个实例都会执行

实例 A ──→ 执行定时任务
实例 B ──→ 执行定时任务(重复!)
实例 C ──→ 执行定时任务(重复!)

✅ 解决方案 1:Redis 分布式锁(推荐)

@Component
public class DistributedScheduler {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Scheduled(cron = "0 0 2 * * ?")
    public void task() {
        String lockKey = "task:dailyReport";
        String requestId = UUID.randomUUID().toString();
        Boolean locked = redisTemplate.opsForValue()
                .setIfAbsent(lockKey, requestId, Duration.ofMinutes(5));

        if (locked != null && locked) {
            try {
                // 执行任务
                doBusiness();
            } finally {
                // 释放锁(Lua 脚本保证原子性)
                String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                redisTemplate.execute(new DefaultRedisScript<>(script, Long.class),
                        Collections.singletonList(lockKey), requestId);
            }
        }
    }
}

✅ 解决方案 2:数据库唯一约束

CREATE TABLE scheduled_lock (
    task_name VARCHAR(50) PRIMARY KEY,
    lock_time DATETIME
);
@Scheduled(cron = "0 0 2 * * ?")
public void task() {
    int rows = lockMapper.tryLock("dailyReport", LocalDateTime.now());
    if (rows == 1) {
        doBusiness();
    }
}

✅ 解决方案 3:XXL-JOB / PowerJob(生产级)

  • 任务调度中心
  • 分片广播
  • 失败重试
  • 任务监控

📌 企业级项目最终都会走向调度中心。


七、Spring Boot 4 新变化

  • 虚拟线程(@Async 可配合 VirtualThreadTaskExecutor
  • 定时任务与 AOT 编译兼容更好
  • 任务执行指标暴露(Micrometer)
@Bean
public Executor taskExecutor() {
    return Executors.newVirtualThreadPerTaskExecutor(); // Java 21+
}

八、常见坑位总结

解决

定时任务串行执行

配置 TaskScheduler 线程池

异步任务线程无限增长

自定义 ThreadPoolTaskExecutor

任务重复执行

Redis 锁 / 调度中心

任务异常无声失败

全局异常处理器 / exceptionally

任务阻塞导致系统卡死

合理设置超时 + 监控

定时任务时间不准

用 cron + 分布式锁


九、本篇总结

  1. @Scheduled 默认串行,必须配置线程池
  2. @Async 必须自定义线程池,拒绝默认配置
  3. 定时 + 异步是黄金组合,避免阻塞调度线程
  4. 并行任务用 CompletableFuture,别用 Future
  5. 分布式环境定时任务必须防重(Redis 锁)
  6. 生产环境最终走向调度中心(XXL-JOB / PowerJob)
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值