Spring Boot 优雅实现异步调用:从入门到自定义线程池与异常处理

在 Spring Boot 项目里,同步接口经常会被慢接口拖垮响应速度。异步调用可以把耗时任务丢到独立线程,让主线程快速返回,提升接口吞吐量与用户体验。

这篇文章用最清晰、最工程化的方式,带你从零学会 Spring Boot 异步开发,包含默认使用、自定义线程池、异常处理、避坑要点。


一、先搞懂:同步 vs 异步

  • 同步:任务串行执行,必须等上一个完成才能走下一个,主线程阻塞。
  • 异步:任务提交给独立线程执行,主线程不用等,直接返回。

适用场景:

  • 发送短信、推送消息
  • 记录日志、上报埋点
  • 调用第三方慢接口
  • 批量数据处理

二、Spring Boot 异步最简实现(3步搞定)

Spring 提供 @Async + @EnableAsync 开箱即用。

1. 启动类/配置类开启异步

复制代码
@SpringBootApplication
@EnableAsync // 开启异步
public class AsyncApplication {
    public static void main(String[] args) {
        SpringApplication.run(AsyncApplication.class, args);
    }
}

2. 编写异步 Service

规则

  • 方法必须是 public

  • 不能是本类内部调用(AOP 代理限制)

  • 返回值只能是 voidFuture<T>

    @Service
    @Slf4j
    public class AsyncServiceImpl {

    复制代码
      // 无返回值异步
      @Async
      public void asyncVoidTask() throws InterruptedException {
          Thread.sleep(3000);
          log.info("异步任务执行,线程:{}", Thread.currentThread().getName());
      }
    
      // 带返回值异步
      @Async
      public Future<String> asyncFutureTask() throws InterruptedException {
          Thread.sleep(5000);
          log.info("异步任务执行,线程:{}", Thread.currentThread().getName());
          return new AsyncResult<>("任务执行完成");
      }

    }

3. Controller 调用

复制代码
@RestController
@Slf4j
public class AsyncController {

    @Autowired
    private AsyncServiceImpl asyncService;

    @GetMapping("/async/void")
    public String testVoid() {
        long start = System.currentTimeMillis();
        log.info("主线程:{}", Thread.currentThread().getName());
        asyncService.asyncVoidTask();
        return "耗时:" + (System.currentTimeMillis() - start) + "ms";
    }

    @GetMapping("/async/future")
    public String testFuture() throws ExecutionException, InterruptedException {
        long start = System.currentTimeMillis();
        Future<String> future = asyncService.asyncFutureTask();
        // 阻塞获取结果
        String result = future.get();
        return result + ",总耗时:" + (System.currentTimeMillis() - start) + "ms";
    }
}

运行结果

  • /async/void:立即返回,耗时几毫秒,异步任务后台执行。
  • /async/future:会阻塞 5 秒,但依然是异步执行。

三、自定义异步线程池(生产必须用)

Spring 默认用 SimpleAsyncTaskExecutor,不重用线程,高并发会OOM。生产环境必须自定义线程池

复制代码
@Configuration
public class AsyncConfig implements AsyncConfigurer {

    @Override
    @Bean("asyncTaskExecutor")
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 核心线程数
        executor.setCorePoolSize(5);
        // 最大线程数
        executor.setMaxPoolSize(10);
        // 队列容量
        executor.setQueueCapacity(200);
        // 线程名前缀
        executor.setThreadNamePrefix("async-task-");
        // 关机时等待任务完成
        executor.setWaitForTasksToCompleteOnShutdown(true);
        // 等待时长
        executor.setAwaitTerminationSeconds(60);
        // 拒绝策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());

        executor.initialize();
        return executor;
    }
}

使用时指定线程池:

复制代码
@Async("asyncTaskExecutor")
public void asyncVoidTask() { ... }

四、自定义异步异常处理器

异步方法里抛异常,主线程感知不到。我们可以统一捕获。

复制代码
@Configuration
public class AsyncConfig implements AsyncConfigurer {

    // 上面的线程池配置...

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return (ex, method, params) -> {
            log.error("异步方法异常:{}", method.getName());
            log.error("异常信息:", ex);
        };
    }
}

效果:

异步任务报错后,不会直接吞掉,会按你定义的方式打印/告警。


五、@Async 工作原理(面试常问)

  1. @EnableAsync 开启 AOP 代理。
  2. @Async 方法被调用时,Spring 拦截并提交到线程池。
  3. 由线程池里的线程执行目标方法。
  4. 主线程立即返回,不阻塞。

六、避坑指南(非常重要)

  1. 异步方法必须是 public,private 不生效。
  2. 不能同类内部调用 ,比如 this.asyncMethod(),不走代理。
  3. 返回值只能是 void / Future ,用 ListenableFuture 也可以。
  4. 生产必须自定义线程池,默认会无限创建线程。
  5. 异常必须单独处理,主线程 catch 不到。
  6. 事务不生效:异步方法和主线程不在一个事务里。

七、总结

Spring Boot 异步非常简单:

  • 开启:@EnableAsync
  • 标记:@Async
  • 生产:自定义线程池 + 异常处理
  • 场景:耗时、非核心、可并行任务

用好异步,能让你的接口性能直接上一个台阶。

降重鸟技术团队文章,勿全文转发

相关推荐
lqqjuly4 分钟前
MLA — 多头潜在注意力深度解析
深度学习·神经网络·算法
是一个Bug5 分钟前
MongoDB:像搭积木一样存数据
数据库·mongodb
ULIi096kr22 分钟前
MySQL解决Too many connections报错:连接数爆满排查、优化与永久解决方案
数据库·mysql·adb
吴可可12324 分钟前
SolidWorks草图转三维DWG技巧
算法
凡人叶枫30 分钟前
Effective C++ 条款04:确定对象被使用前已先被初始化
java·linux·开发语言·c++·嵌入式开发
极客先躯36 分钟前
高级java每日一道面试题-2026年02月01日-实战篇[Docker]-Docker Volume 的生命周期管理是怎样的?
java·运维·docker·容器·持久化·架构图·容器卷
NE_STOP1 小时前
Raft算法处理细节
java
redaijufeng1 小时前
C++雾中风景7:闭包
c++·算法·风景
努力攻坚操作系统1 小时前
编程语言编译运行机制对比:C / Java / Python
java·c语言·python
SL-staff1 小时前
(一)数据源配置 —— JVS-Rules规则引擎 V2.5 操作说明介绍
数据库·jar·规则引擎·数据源·jvs-rules·api 接口·jvs低代码