今天遇到一个很诡异的问题,我们有一个业务实现是将一个uuid存入到redis当中的,前端会一直调用获取的接口,直到拿到数据,但是有时候发现却卡主了,一直获取不到
首先排查redis,发现redis中没有这个uuid对应的数据
第一步考虑是不是redis阻塞了 赛数据的时候根本没存进去,通过打日志发现,根本就没走到这一步
第二步找这查询不到的几个接口的共同点,发现都用了线程池 使用的
CompletableFuture异步去处理的,想着有没有可能是多线程阻塞了,又有很多地方都用了这个类去实现多线程。后来发现这些
CompletableFuture在使用的时候
CompletableFuture<String> futureEngineering = CompletableFuture.supplyAsync(() -> generateEngineering(userId,bidName,bidSectionDTO.getEngineeringDemand()));
没有配置
executor参数,排查可能是这个原因因为
如果不加 executor,CompletableFuture 会使用默认的 ForkJoinPool.commonPool() 作为线程池,可能会导致以下问题:
1. 线程资源无法控制:
- 默认线程池大小依赖于 CPU 核心数
- 无法限制最大线程数
- 可能导致资源耗尽
2. 任务阻塞风险:3. 性能问题:
- 所有异步任务共享同一个线程池
- 可能相互影响执行效率
- 无法针对业务特点优化
CompletableFuture 使用默认线程池和自定义线程池的区别:
# CompletableFuture线程池对比分析
## 1. 默认线程池 (ForkJoinPool.commonPool())
### 1.1 特点
- 线程数 = CPU核心数 - 1
- 所有CompletableFuture共享同一个线程池
- 适合CPU密集型计算任务
- 无法自定义配置
### 1.2 示例代码
```java
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
return "result";
});
3. 使用场景对比
3.1 默认线程池适用场景
- 简单的异步任务
- CPU密集型计算
- 临时测试和开发
- 任务数量可控的场景
3.2 自定义线程池适用场景
2. 自定义线程池
2.1 特点
- 可自定义线程数量
- 可配置队列大小
- 可设置拒绝策略
- 适合IO密集型任务
- 便于监控和管理
2.2 示例代码
ThreadPoolExecutor executor = new ThreadPoolExecutor(
5, // 核心线程数
10, // 最大线程数
60L, TimeUnit.SECONDS,// 空闲线程存活时间
new LinkedBlockingQueue<>(100) // 队列大小
);
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
return "result";
}, executor);
- 生产环境应用
- IO密集型操作
- 需要资源隔离
- 大量并发任务
- 需要监控和管理
4. 性能影响
4.1 默认线程池
- 受CPU核心数限制
- 可能与其他任务竞争资源
- 无法针对业务优化
4.2 自定义线程池
- 可根据业务调整配置
- 资源隔离,避免互相影响
- 可以进行性能优化
5. 最佳实践建议
- 开发测试:可以使用默认线程池
- 生产环境:建议使用自定义线程池
- IO密集型:使用自定义线程池,线程数可以设置较大
- CPU密集型:使用默认线程池或设置较小的线程数