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 密集型、需要事务的场景别乱用。

相关推荐
月落归舟1 分钟前
Java线程小记
java·开发语言
西凉的悲伤3 分钟前
Spring Cloud Gateway介绍
java·spring cloud·gateway
摇滚侠15 分钟前
01 基础语法 JavaScript 入门到精通全套教程
开发语言·javascript·ecmascript
sleven fung15 分钟前
Milvus 向量数据库
开发语言·数据库·python·langchain·milvus
逸Y 仙X27 分钟前
文章五:Elasticsearch安全通信
java·大数据·安全·elasticsearch·搜索引擎·全文检索·jenkins
quan263137 分钟前
20260529,日常开发-查老数据全量更新闭坑
java·mysql·主从·主从延迟
大大杰哥39 分钟前
Java 日志框架详解:SLF4J + Logback 从入门到实战
java·开发语言·logback
ylscode1 小时前
黑客利用 GHOSTYNETWORKS 和 OMEGATECH 托管 JS 恶意软件基础设施
开发语言·安全·php·安全威胁分析
爱吃生蚝的于勒1 小时前
QT开发第二章——信号和槽
c语言·开发语言·c++·qt
xcLeigh1 小时前
Python入门:Python3 operator模块全面学习教程
开发语言·python·学习·教程·python3·operator