一、场景背景
在实际业务开发中,查询分类及关联标签是非常常见的业务场景:
- 先查询一级 / 二级分类列表
- 遍历每个分类,查询其关联的标签数据
- 封装结果返回给前端
如果使用串行遍历查询:分类数量越多,接口响应时间越长,性能极差。 因此,本文采用自定义线程池 + FutureTask 异步并发查询方案,大幅提升接口响应速度,同时保证代码优雅性与可维护性。
二、使用方案:
- 线程池:自定义 ThreadPoolExecutor,复用线程,避免频繁创建销毁线程带来的开销
- FutureTask:支持带返回值的异步任务,可并发执行并统一获取结果
- 并发查询:并行查询每个分类的标签数据,而非串行执行
- 结果聚合:统一收集异步任务结果,封装成前端需要的数据结构
三、代码实现
1. 自定义线程池配置
首先定义线程池,交给 Spring 管理,控制并发线程数、队列、拒绝策略。
/**
* 线程池配置类
* 用于分类标签并发查询
*/
@Configuration
public class ThreadPoolConfig {
/**
* 标签查询专用线程池
*/
@Bean(name = "labelThreadPool")
public ThreadPoolExecutor getLabelThreadLocal() {
return new ThreadPoolExecutor(
// 核心线程数
20,
// 最大线程数
100,
// 空闲线程存活时间
5,
// 时间单位
TimeUnit.SECONDS,
// 阻塞队列容量40
new LinkedBlockingDeque<>(40),
// 默认线程工厂
Executors.defaultThreadFactory(),
// 拒绝策略:调用者线程执行
new ThreadPoolExecutor.CallerRunsPolicy()
);
}
}
线程池参数说明
| 参数 | 值 | 说明 |
|---|---|---|
| 核心线程数 | 20 | 长期存活的线程数 |
| 最大线程数 | 100 | 并发峰值最大线程数 |
| 空闲时间 | 5s | 超出核心线程的线程空闲存活时间 |
| 阻塞队列 | 40 | 任务排队队列 |
| 拒绝策略 | CallerRunsPolicy | 队列 / 线程满后,由提交任务的主线程执行,保证不丢失任务 |
2. 业务层:并发查询分类 + 标签
核心业务逻辑:
- 查询全部分类
- 为每个分类创建一个 FutureTask 异步任务
- 提交到线程池并发执行
- 遍历 FutureTask 获取所有结果并聚合
- 封装最终结果返回
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.web.bind.annotation.Resource;
import java.util.*;
import java.util.concurrent.FutureTask;
import java.util.stream.Collectors;
/**
* 分类标签业务实现类
* 线程池并发查询优化
*/
@Slf4j
@Service
public class SubjectCategoryServiceImpl implements SubjectCategoryService {
// 注入自定义线程池
@Resource
private ThreadPoolExecutor labelThreadPool;
// 其他Service依赖
@Resource
private SubjectCategoryService subjectCategoryService;
@Resource
private SubjectMappingService subjectMappingService;
@Resource
private SubjectLabelService subjectLabelService;
/**
* 查询分类及关联标签(并发优化版)
*/
@Override
@SneakyThrows
public List<SubjectCategoryBO> queryCategoryAndLabel(SubjectCategoryBO subjectCategoryBO) {
// 1. 查询当前大类下的所有分类
SubjectCategory subjectCategory = new SubjectCategory();
subjectCategory.setParentId(subjectCategoryBO.getId());
subjectCategory.setIsDeleted(IsDeletedFlagEnum.UN_DELETED.getCode());
List<SubjectCategory> subjectCategoryList = subjectCategoryService.queryCategory(subjectCategory);
// 实体转换
List<SubjectCategoryBO> categoryBOList = SubjectCategoryConverter.INSTANCE
.convertCategoryToBoList(subjectCategoryList);
if (CollectionUtils.isEmpty(categoryBOList)) {
return Collections.emptyList();
}
// 2. 创建FutureTask集合,用于并发查询标签
List<FutureTask<Map<Long, List<SubjectLabelBO>>>> futureTaskList = new LinkedList<>();
// 3. 遍历分类,为每个分类创建异步任务
categoryBOList.forEach(category -> {
FutureTask<Map<Long, List<SubjectLabelBO>>> futureTask = new FutureTask<>(() -> getLabelBOList(category));
futureTaskList.add(futureTask);
// 提交至线程池执行
labelThreadPool.submit(futureTask);
});
// 4. 统一获取所有异步任务结果
Map<Long, List<SubjectLabelBO>> resultMap = new HashMap<>();
for (FutureTask<Map<Long, List<SubjectLabelBO>>> futureTask : futureTaskList) {
Map<Long, List<SubjectLabelBO>> map = futureTask.get();
if (CollectionUtils.isNotEmpty(map)) {
resultMap.putAll(map);
}
}
// 5. 封装标签数据到分类BO中
categoryBOList.forEach(bo -> bo.setLabelBOList(resultMap.get(bo.getId())));
return categoryBOList;
}
/**
* 查询单个分类下的标签(异步执行的方法)
*/
private Map<Long, List<SubjectLabelBO>> getLabelBOList(SubjectCategoryBO category) {
// 查询分类-标签映射关系
SubjectMapping mapping = new SubjectMapping();
mapping.setCategoryId(category.getId());
mapping.setIsDeleted(IsDeletedFlagEnum.UN_DELETED.getCode());
List<SubjectMapping> mappingList = subjectMappingService.queryLabelId(mapping);
if (CollectionUtils.isEmpty(mappingList)) {
return null;
}
// 提取标签ID
List<Long> labelIdList = mappingList.stream()
.map(SubjectMapping::getLabelId)
.collect(Collectors.toList());
// 批量查询标签
List<SubjectLabel> labelList = subjectLabelService.batchQueryById(labelIdList);
// 转换BO
List<SubjectLabelBO> labelBOList = new LinkedList<>();
labelList.forEach(label -> {
SubjectLabelBO labelBO = new SubjectLabelBO();
labelBO.setId(label.getId());
labelBO.setLabelName(label.getLabelName());
labelBO.setCategoryId(label.getCategoryId());
labelBO.setSortNum(label.getSortNum());
labelBOList.add(labelBO);
});
// 封装成Map,方便聚合
Map<Long, List<SubjectLabelBO>> resultMap = new HashMap<>();
resultMap.put(category.getId(), labelBOList);
return resultMap;
}
}
四、代码核心知识点讲解
1. FutureTask 作用
- 用于异步执行任务并获取返回值
- 实现
Callable,可以返回结果 - 配合线程池实现并发
FutureTask<Map<Long,List<SubjectLabelBO>>> futureTask = new FutureTask<>(()-> getLabelBOList(category));
2. 并发执行流程
- 分类查询 → 得到分类列表
- 为每个分类创建一个独立异步任务
- 批量提交线程池,并行查询标签
- 调用
futureTask.get()获取结果 - 合并所有结果,封装返回
3. 线程池优势
- 避免为每个请求创建大量线程
- 控制并发数,防止压垮数据库
- 线程复用,减少资源消耗