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
  • 生产:自定义线程池 + 异常处理
  • 场景:耗时、非核心、可并行任务

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

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

相关推荐
woxihuan12345616 小时前
如何为禁用按钮添加点击提示信息
jvm·数据库·python
jran-16 小时前
Docker dockerfile镜像制作&compose服务编排&私有仓库
java·docker·容器
ㄟ留恋さ寂寞16 小时前
Golang怎么限制请求Body大小_Golang如何防止客户端发送过大的请求体【避坑】
jvm·数据库·python
Chase_______16 小时前
【Java杂项】0.1 + 0.2 为什么不等于 0.3?IEEE 754 与 BigDecimal 精度避坑
java·开发语言·python
ch.ju16 小时前
Java Programming Chapter 4——Static part
java·开发语言
SimpleLearingAI16 小时前
大模型推理框架总结解析
算法
老纪16 小时前
CSS Flex布局中如何实现导航栏与Logo的左右分布_利用justify-content- space-between
jvm·数据库·python
YDS82916 小时前
DeepSeek RAG&MCP + Agent智能体项目 —— 环境搭建和项目初始化
java·springboot·agent·rag·deepseek
会编程的土豆16 小时前
Go ini 配置加载:`ini.MapTo` 详细解析
开发语言·数据库·golang
ChoSeitaku16 小时前
04.数组
java·开发语言·数据结构