Administrator
Published on 2026-07-30 / 0 Visits
0

SpringBoot @Async 异步 + @Scheduled 定时任务完整配置详解

#AI

SpringBoot @Async 异步 + @Scheduled 定时任务完整配置详解

一、基础概念区分

  1. @Scheduled:定时任务,同步阻塞执行,单线程串行执行多个定时任务,一个任务阻塞会拖慢其他任务。
  2. @Async:方法异步执行,将方法提交至独立线程池,不阻塞主线程;可搭配 @Scheduled 实现异步定时任务
  3. 核心注解配套开启:
    • @EnableScheduling:开启定时任务扫描
    • @EnableAsync:开启异步方法扫描

第一部分:@Scheduled 定时任务完整配置

1. 开启定时任务

启动类添加 @EnableScheduling

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableScheduling // 开启定时任务支持
public class ScheduleApplication {
    public static void main(String[] args) {
        SpringApplication.run(ScheduleApplication.class, args);
    }
}

2. @Scheduled 四种执行规则

(1)fixedRate:固定频率(不管任务是否执行完,间隔固定毫秒再次执行)

// 每2秒执行一次,上一次没执行完,2秒后照样新开线程(同步模式下会排队阻塞)
@Scheduled(fixedRate = 2000)
public void taskRate() {
    System.out.println("fixedRate定时:" + System.currentTimeMillis());
    try {
        Thread.sleep(3000); // 任务耗时3秒 > 间隔2秒
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

(2)fixedDelay:固定延迟(上一次执行完成后,再等待指定毫秒执行下一次,最常用)

// 任务执行完毕后,等待3秒再执行下一次
@Scheduled(fixedDelay = 3000)
public void taskDelay() {
    System.out.println("fixedDelay定时:" + System.currentTimeMillis());
}

(3)initialDelay:首次启动延迟

搭配 fixedRate/fixedDelay 使用,项目启动后延迟N毫秒才第一次执行

// 项目启动5秒后第一次执行,之后每2秒执行一次
@Scheduled(fixedRate = 2000, initialDelay = 5000)
public void taskInitDelay() {
}

(4)cron 表达式(自定义复杂时间,最灵活)

格式:秒 分 时 日 月 周 年(可选)
字段范围:

  1. 秒:0-59
  2. 分:0-59
  3. 时:0-23
  4. 日:1-31
  5. 月:1-12 / JAN-DEC
  6. 周:1-7 / SUN-SAT(1=周日,7=周六)
  7. 年:可选 1970-2099

常用示例:

// 每10秒执行一次
@Scheduled(cron = "0/10 * * * * ?")
// 每天凌晨2点执行
@Scheduled(cron = "0 0 2 * * ?")
// 每周一早上9点30分
@Scheduled(cron = "0 30 9 ? * MON")
// 每月1号凌晨1点
@Scheduled(cron = "0 0 1 1 * ?")
public void cronTask() {
}

注意:cron 日和周不能同时指定,其中一个必须用 ? 占位

3. 配置文件外部化 cron(推荐,不用改代码重启)

application.yml

task:
  cron: 0/5 * * * * ?
  delay: 3000

代码读取配置:

// 读取yml中的cron表达式
@Scheduled(cron = "${task.cron}")
public void configCronTask(){}

// 读取延迟数值
@Scheduled(fixedDelayString = "${task.delay}")
public void configDelayTask(){}

4. 默认定时任务线程池缺陷(重点)

Spring 默认只提供单线程执行所有 @Scheduled 任务:

  • 多个定时任务串行执行
  • 某一个任务耗时过长、阻塞、异常,会导致其他任务全部延迟、错过执行时机

解决方案:自定义定时任务线程池(TaskScheduler)

自定义定时任务线程池配置

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

@Configuration
public class SchedulePoolConfig {

    @Bean
    public TaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setPoolSize(10); // 核心线程数,同时并行执行10个定时任务
        scheduler.setThreadNamePrefix("schedule-task-"); // 线程名前缀
        scheduler.setWaitForTasksToCompleteOnShutdown(true); // 关闭时等待任务执行完
        scheduler.setAwaitTerminationSeconds(60); // 等待超时60秒
        return scheduler;
    }
}

第二部分:@Async 异步配置(单独异步 + 搭配定时任务异步)

1. 开启异步支持

启动类追加 @EnableAsync

@SpringBootApplication
@EnableScheduling
@EnableAsync // 开启异步
public class ScheduleApplication {}

2. @Async 基础使用

2.1 无返回值异步

@Service
public class AsyncService {

    @Async // 该方法异步执行
    public void asyncMethod() {
        System.out.println("异步线程:" + Thread.currentThread().getName());
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

2.2 有返回值异步(Future接收结果)

@Async
public Future<String> asyncReturn() {
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    return new AsyncResult<>("异步执行完成");
}

// 调用
@Autowired
private AsyncService asyncService;

public void test() throws ExecutionException, InterruptedException {
    Future<String> future = asyncService.asyncReturn();
    String res = future.get(); // 阻塞获取结果
}

3. 自定义异步线程池(必配,默认线程池性能差)

Spring 默认异步线程池 SimpleAsyncTaskExecutor 无上限,并发高会无限创建线程,必须自定义 ThreadPoolTaskExecutor

异步线程池配置类

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

@Configuration
@EnableAsync
public class AsyncPoolConfig {

    @Bean("asyncTaskExecutor") // 指定线程池名称,可在@Async指定使用
    public Executor asyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 核心线程数
        executor.setCorePoolSize(8);
        // 最大线程数
        executor.setMaxPoolSize(20);
        // 队列容量
        executor.setQueueCapacity(100);
        // 线程前缀
        executor.setThreadNamePrefix("async-thread-");
        // 空闲线程存活时间
        executor.setKeepAliveSeconds(60);
        // 拒绝策略:任务满了由调用者线程执行
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
}

指定使用自定义线程池

// 使用名为 asyncTaskExecutor 的线程池执行
@Async("asyncTaskExecutor")
public void customPoolAsync(){}

4. @Scheduled + @Async 组合:异步定时任务

场景:定时任务执行耗时久,不想阻塞其他定时任务,让当前定时任务独立异步执行。

写法1:方法上加 @Async

@Service
public class AsyncScheduleTask {

    // 每5秒执行,且任务异步执行
    @Scheduled(fixedRate = 5000)
    @Async("asyncTaskExecutor") // 使用自定义异步线程池
    public void asyncScheduleTask() {
        System.out.println("异步定时任务线程:" + Thread.currentThread().getName());
        try {
            Thread.sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

执行逻辑说明

  1. 定时任务调度线程池(TaskScheduler)触发定时
  2. 检测到 @Async,将方法提交至异步线程池执行
  3. 调度线程立刻释放,不会阻塞下一次定时触发
  4. 多个定时任务互不影响,任务内部耗时不影响调度频率

5. @Async 常见限制(避坑)

  1. 不能同类内部调用
    同一个类中 this.asyncMethod() 调用异步方法,注解失效,不会异步。
    解决:通过 ApplicationContext / 自身注入 @Autowired private AsyncService self; 调用。
  2. 方法必须是 public,private/protected 不生效
  3. 静态方法加 @Async 失效
  4. 异步方法内部事务失效(独立线程,事务上下文隔离)

第三部分:定时任务异常处理

1. 同步定时任务异常

默认抛出异常后,仅当前任务终止,不影响下一次调度。

2. 异步定时任务异常

无返回值的 @Async 方法异常会直接丢失日志,需要全局捕获:

方案1:自定义 AsyncUncaughtExceptionHandler

@Component
public class GlobalAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
    @Override
    public void handleUncaughtException(Throwable ex, Method method, Object... params) {
        System.err.println("异步任务异常:" + method.getName() + ",异常:" + ex.getMessage());
        ex.printStackTrace();
    }
}

注入到异步线程池:

executor.setAsyncUncaughtExceptionHandler(new GlobalAsyncExceptionHandler());

方案2:try-catch 包裹任务逻辑(最简单)

@Scheduled(fixedRate = 3000)
@Async
public void task() {
    try {
        // 业务逻辑
    } catch (Exception e) {
        // 日志记录、告警
    }
}

第四部分:完整整合示例(可直接复制运行)

1. 启动类

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

2. 定时任务线程池配置 ScheduleConfig

@Configuration
public class ScheduleConfig {
    @Bean
    public TaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setPoolSize(8);
        scheduler.setThreadNamePrefix("schedule-pool-");
        scheduler.setWaitForTasksToCompleteOnShutdown(true);
        return scheduler;
    }
}

3. 异步线程池 AsyncConfig

@Configuration
public class AsyncConfig {
    @Bean("businessAsyncPool")
    public Executor asyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(30);
        executor.setQueueCapacity(200);
        executor.setThreadNamePrefix("business-async-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.setAsyncUncaughtExceptionHandler(new GlobalAsyncExceptionHandler());
        executor.initialize();
        return executor;
    }
}

4. 定时任务业务类

@Service
public class ScheduleTaskService {

    // 同步定时,多任务会串行阻塞
    @Scheduled(fixedDelay = 2000)
    public void syncTask() {
        System.out.println("同步定时任务:" + Thread.currentThread().getName());
    }

    // 异步定时,独立线程执行,不阻塞调度
    @Scheduled(cron = "0/5 * * * * ?")
    @Async("businessAsyncPool")
    public void asyncTask() {
        try {
            System.out.println("异步定时任务:" + Thread.currentThread().getName());
            Thread.sleep(3000);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

第五部分:生产环境最佳实践

  1. 必须自定义 TaskScheduler:扩大定时调度线程数,避免单线程阻塞所有任务
  2. 必须自定义 Async 线程池:控制并发,防止无限创建线程 OOM
  3. 长耗时定时任务统一加 @Async,实现异步调度
  4. cron 表达式放到配置文件,方便线上动态修改无需改代码
  5. 所有定时、异步任务增加 try-catch + 日志告警(邮件/短信)
  6. 服务优雅关闭:配置 setWaitForTasksToCompleteOnShutdown(true),停机前执行完正在运行的任务
  7. 区分两种线程池:
    • TaskScheduler:负责触发定时(调度线程)
    • ThreadPoolTaskExecutor:负责执行业务(异步工作线程)