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

相关推荐
wifi___4 小时前
全局异常处理的原理
java·开发语言
平常心的技术小牛7 小时前
Qt-快速上手-QMenuBar
开发语言·qt
青 春 记 忆7 小时前
零基础入门Python11|Git实战:为任务管理器建立版本历史
开发语言·git·vscode·python·python3.11
2601_963870208 小时前
【计算机毕业设计】基于Spring Boot的专科医院医疗管理系统
java·spring boot·课程设计
Bingo_BIG8 小时前
Java Spring 批量修改,实体、接口、方法的定义
java·spring
画中有画9 小时前
软件架构中质量属性(性能、安全、可扩展性)的权衡设计
java·运维·安全
Shaoxi Zhang9 小时前
JAVA学习笔记035——对象和JSON格式
java·笔记·学习
赵丙双9 小时前
CountDownLatch 源码分析
java·aqs·countdownlatch
余额瞒着我当琳9 小时前
C++--深拷贝三件套 + swap + 写时拷贝 + vector 扩容 + reserve
java·开发语言·c++
thefool11226610 小时前
翻转二叉树
java