本教程将带你从零开始,在 Spring Boot 环境中配置一个专业的线程池 ,并用 CompletableFuture 优雅地编排异步任务。同时会覆盖异常处理、多任务聚合、超时控制等常见场景。
1. 为什么需要自定义线程池?
- 默认线程池风险 :
CompletableFuture.supplyAsync()默认使用ForkJoinPool.commonPool(),该池在 Web 应用中极易被耗尽,导致任务阻塞。 - 隔离与监控:为不同业务(如订单处理、报表导出)创建独立的线程池,避免相互影响,并便于监控。
- Spring 生态友好 :通过
ThreadPoolTaskExecutor管理生命周期,Spring 容器关闭时自动优雅停机。
2. 配置类:定义一个生产级线程池 Bean
java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
@Configuration
public class ThreadPoolConfig {
@Bean("bizExecutor")
public Executor businessExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 核心线程数:CPU核心数 * 2(适用于IO密集型业务)
int cores = Runtime.getRuntime().availableProcessors();
executor.setCorePoolSize(cores * 2);
// 最大线程数:核心线程数 * 2
executor.setMaxPoolSize(cores * 4);
// 队列容量:根据业务量调整
executor.setQueueCapacity(200);
// 线程空闲存活时间(秒)
executor.setKeepAliveSeconds(60);
// 线程名前缀(方便排查问题)
executor.setThreadNamePrefix("biz-pool-");
// 拒绝策略:由调用线程执行(保证任务不丢失,但会阻塞调用方)
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 优雅关闭:等待所有任务完成再销毁
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(30);
executor.initialize();
return executor;
}
}
参数解读:
corePoolSize:常驻线程数。IO 密集型任务建议2 * CPU 核数。maxPoolSize:队列满后最多创建的线程数。queueCapacity:有界队列,避免内存溢出。CallerRunsPolicy:当线程池饱和时,让调用线程自己执行(降级策略)。- 实际生产中可以根据监控动态调整。
3. Service 层:使用 CompletableFuture + 自定义线程池
定义一个 AsyncService,演示各种常用模式。
java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@Service
public class AsyncService {
private static final Logger log = LoggerFactory.getLogger(AsyncService.class);
@Autowired
@Qualifier("bizExecutor")
private Executor bizExecutor;
// 模拟耗时操作
private void sleep(int millis) {
try { Thread.sleep(millis); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
}
// ========== 1. 简单的异步任务 ==========
public CompletableFuture<String> fetchUserInfoAsync(Long userId) {
return CompletableFuture.supplyAsync(() -> {
log.info("开始获取用户信息,userId={}", userId);
sleep(500);
return "User-" + userId;
}, bizExecutor);
}
// ========== 2. 串行链式调用 ==========
public CompletableFuture<String> buildWelcomeMessage(Long userId) {
return fetchUserInfoAsync(userId)
.thenApply(userName -> {
// 查询用户等级(模拟)
int level = (int) (Math.random() * 5);
return String.format("%s (Lv.%d)", userName, level);
})
.thenApply(richInfo -> "Welcome back, " + richInfo);
}
// ========== 3. 两个独立任务合并结果 ==========
public CompletableFuture<Integer> computeFinalPrice() {
CompletableFuture<Integer> priceFuture = CompletableFuture.supplyAsync(() -> {
sleep(300);
return 100; // 价格
}, bizExecutor);
CompletableFuture<Integer> couponFuture = CompletableFuture.supplyAsync(() -> {
sleep(200);
return 20; // 优惠券
}, bizExecutor);
return priceFuture.thenCombine(couponFuture, (price, coupon) -> price - coupon);
}
// ========== 4. 多任务并发 -- 等待全部完成(带异常保护) ==========
public void test(){
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "value1");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "value2");
CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> "value3");
CompletableFuture<String> future4 = CompletableFuture.supplyAsync(() -> {
if(1==1)throw new RuntimeException();
return "111";
}).exceptionally(ex-> "-1");
// allOf 等待所有完成
CompletableFuture.allOf(future1, future2, future3,future4).join();
String join1 = future1.join();
String join2 = future2.join();
String join3 = future3.join();
String join4 = future4.join();
System.out.println("join1:"+join1+",join2:"+join2+",join3:"+join3+",join4:"+join4);
// join1:value1,join2:value2,join3:value3,join4:-1
}
// ========== 5. 超时控制 (Java 9+) ==========
public CompletableFuture<String> fetchWithTimeout() {
return CompletableFuture.supplyAsync(() -> {
sleep(2000); // 模拟长耗时
return "慢任务结果";
}, bizExecutor)
.orTimeout(1, TimeUnit.SECONDS) // 超时1秒
.exceptionally(ex -> {
log.error("任务超时", ex);
return "超时默认值";
});
}
}
4. Controller:暴露接口测试
java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@RestController
@RequestMapping("/async")
public class AsyncController {
@Autowired
private AsyncService asyncService;
@GetMapping("/user/{id}")
public CompletableFuture<String> getUser(@PathVariable Long id) {
return asyncService.fetchUserInfoAsync(id);
}
@GetMapping("/welcome/{id}")
public CompletableFuture<String> welcome(@PathVariable Long id) {
return asyncService.buildWelcomeMessage(id);
}
@GetMapping("/price")
public CompletableFuture<Integer> price() {
return asyncService.computeFinalPrice();
}
@GetMapping("/multi")
public CompletableFuture<List<String>> multi() {
return asyncService.fetchMultiData(Arrays.asList("a", "b", "error", "c"));
}
@GetMapping("/timeout")
public CompletableFuture<String> timeout() {
return asyncService.fetchWithTimeout();
}
}
5. 运行与验证
启动 Spring Boot 应用后,可以依次请求:
GET /async/user/1001→ 异步返回"User-1001"GET /async/welcome/1002→ 返回"Welcome back, User-1002 (Lv.3)"GET /async/price→ 返回80(100 - 20)GET /async/multi→ 返回["Data-a", "Data-b", "默认值", "Data-c"]GET /async/timeout→ 返回"超时默认值"
观察控制台日志,线程名会显示为 biz-pool-1、biz-pool-2 等,说明任务运行在自定义线程池中。
6. 常见陷阱与最佳实践
| 问题 | 解决方案 |
|---|---|
忘记传入 executor |
所有 *Async 方法必须显式传入自定义线程池,否则使用 commonPool。 |
allOf 静默失败 |
使用 exceptionally 为每个子任务兜底,避免整体结果缺失。 |
| 线程池配置不合理 | IO 密集型:核心=2*CPU;CPU 密集型:核心=CPU+1;队列不宜过大。 |
未处理 InterruptedException |
在 sleep 或阻塞操作中要重置中断标志。 |
| Spring 关闭时任务丢失 | 使用 setWaitForTasksToCompleteOnShutdown(true) 并设置合理的等待时间。 |
滥用 join() 阻塞 |
Controller 返回 CompletableFuture 即可异步处理请求,不要手动 join()。 |
| 监控缺失 | 暴露 ThreadPoolTaskExecutor 的指标(活跃线程数、队列大小、拒绝次数)到 Prometheus。 |
7. 扩展:为不同业务配置多个线程池
java
@Bean("reportExecutor")
public Executor reportExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(4);
executor.setQueueCapacity(50);
executor.setThreadNamePrefix("report-");
// ... 其他配置
return executor;
}
// 使用时通过 @Qualifier 区分
@Autowired @Qualifier("reportExecutor") private Executor reportExecutor;