SpringBoot 异步处理与线程池实战

先问一个问题:你的项目里,有没有类似这样的代码?

java 复制代码
new Thread(() -> {
    // 发送邮件、记录日志、调用外部接口
}).start();

new Thread() 的问题很明显:每次请求都创建新线程,用完就丢,线程数量无上限,分分钟把系统搞崩。

正确的做法是使用线程池。SpringBoot 内置了强大的异步支持,几行配置就能搞定。

一、快速上手

1.1 开启异步

在启动类或配置类上加一个注解:

java 复制代码
@SpringBootApplication
@EnableAsync   // 就这一行
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

1.2 写一个异步方法

java 复制代码
@Service
@Slf4j
public class NotificationService {
    
    @Async   // 加了这个注解,方法就变成异步了
    public void sendEmail(String to, String content) {
        log.info("发送邮件到: {}", to);
        // 耗时操作...
    }
}

1.3 调用

java 复制代码
@RestController
public class UserController {
    
    @Autowired
    private NotificationService notificationService;
    
    @PostMapping("/register")
    public String register(String username, String email) {
        // 主流程立即返回,邮件在后台慢慢发
        notificationService.sendEmail(email, "欢迎注册");
        return "注册成功";
    }
}

效果:调用方不会等待邮件发送完成,接口会立即返回。

二、三个必知的坑

现象 解决方案
内部调用不生效 同一个类的方法调用 @Async 方法,还是同步执行 把异步方法放到单独的 Service 中
private 不生效 异步方法写成 private 必须是 public
返回普通对象会失效 返回 User、String 等 只能返回 void 或 CompletableFuture

三、自定义线程池(生产必做)

默认的线程池太简陋,生产环境一定要自己配置:

java 复制代码
@Configuration
@EnableAsync
public class AsyncConfig {
    
    @Bean(name = "taskExecutor")
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);        // 核心线程数
        executor.setMaxPoolSize(20);        // 最大线程数
        executor.setQueueCapacity(100);     // 队列大小
        executor.setThreadNamePrefix("async-"); // 线程名前缀
        executor.setRejectedExecutionHandler(
            new ThreadPoolExecutor.CallerRunsPolicy() // 满了就让调用方执行
        );
        executor.initialize();
        return executor;
    }
}

参数怎么设?

场景 corePoolSize maxPoolSize queueCapacity
轻量任务多 10-20 50-100 200-500
重量任务多 2-5 10-20 100-200
IO 密集型 CPU核数×2 CPU核数×4 200左右

使用自定义线程池:

java 复制代码
@Async("taskExecutor")  // 指定线程池名称
public void sendEmail(String to, String content) {
    // ...
}

四、处理异步任务异常

异步方法抛出的异常,调用方是抓不到的。需要自定义异常处理器:

java 复制代码
@Configuration
public class AsyncExceptionConfig implements AsyncConfigurer {
    
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return (ex, method, params) -> {
            log.error("异步方法执行失败: {}.{}", 
                method.getDeclaringClass().getName(), 
                method.getName(), ex);
            // 可以在这里发送告警
        };
    }
}

五、有返回值的异步

如果需要拿到异步执行的结果:

java 复制代码
@Async
public CompletableFuture<String> fetchData() {
    // 模拟耗时
    Thread.sleep(3000);
    return CompletableFuture.completedFuture("结果数据");
}

// 调用方
CompletableFuture<String> future = dataService.fetchData();
String result = future.get(5, TimeUnit.SECONDS);  // 等待最多5秒

总结

要点 一句话
开启 @EnableAsync
使用 @Async
线程池 必须自定义,别用默认的
异常 单独处理,别丢了
返回值 void 或 CompletableFuture

最后记住一句话:

异步不是银弹。IO 密集型、不需要返回结果的场景用它;CPU 密集型、需要事务的场景别乱用。

相关推荐
行者全栈架构师5 小时前
IDEA 中 Maven 项目的 15 个红色报错快速解决方法
java·后端
令人头秃的代码0_05 小时前
mac(m5)平台编译openjdk
java
唐青枫1 天前
Java JDBC 实战指南:从 Connection 到事务和连接池
java
一个做软件开发的牛马1 天前
MyBatis-Plus 从零实战:完整搭建可运行 Demo,BaseMapper 零 SQL、Wrapper 条件构造、分页插件与代码生成器详解
java·后端
用户3721574261351 天前
Java 处理 PDF 图片:提取 PDF 中的图片,并压缩 PDF 图片体积
java
用户3721574261351 天前
Java 打印 Word 文档:从基础打印到高级设置
java
用户3521802454752 天前
当 Prompt 学会"热更新":Spring Boot × Nacos3 AI 实战
java·spring boot·ai编程
东坡白菜2 天前
破局全栈:一个前端开发的Java入门实战记录(1)
java·全栈
唐青枫2 天前
Java Tomcat 实战指南:从 Servlet 容器到 Spring Boot 部署
java
wsaaaqqq2 天前
roudan:自由选择实体、灵活操作数据、快速写入数据库的 Java 框架
java