一、前言
在原有的一次性查询分类及标签的方法中,采用的为先查询小分类对象,对小分类集合进行foreach,让每一个对象再去查询自己的标签集合,但当小分类数量较多时,可使用多线程进行异步查询优化,提高查询性能。
原代码
@Override
@SneakyThrows
public List<SubjectCategoryBO> queryCategoryAndLabel(SubjectCategoryBO subjectCategoryBO) {
if(log.isInfoEnabled()) {
log.info("SubjectCategoryDomainService.queryCategoryAndLabel.bo:{}", JSON.toJSONString(subjectCategoryBO));
}
//查询大分类下的小分类
SubjectCategory subjectCategory = new SubjectCategory(); //bo中代入了id,此处不使用convert转化
subjectCategory.setParentId(subjectCategoryBO.getId());
subjectCategory.setCategoryType(CategoryTypeEnum.SECOND.getCode());
subjectCategory.setIsDeleted(IsDeletedFlagEnum.UN_DELETED.getCode());
List<SubjectCategory> subjectCategoryList = subjectCategoryService.queryCategory(subjectCategory);
List<SubjectCategoryBO> subjectCategoryBOList = SubjectCategoryBOConverter.INSTANCE.categoryListToBOList(subjectCategoryList);
//一次获取标签信息
if(!CollectionUtils.isEmpty(subjectCategoryBOList)) {
subjectCategoryBOList.forEach(bo -> {
SubjectMapping subjectMapping = new SubjectMapping();
subjectMapping.setCategoryId(bo.getId());
//获取 题目_标签_分类 的实体对象
List<SubjectMapping> subjectMappingList = subjectMappingService.queryByLabelId(subjectMapping);
if (CollectionUtils.isEmpty(subjectMappingList)) {
return null;
}
//根据标签id获取标签对象
List<Long> labelIds = subjectMappingList.stream().map(SubjectMapping::getLabelId).collect(Collectors.toList());
List<SubjectLabel> subjectLabelList = subjectLabelService.queryByLabelIds(labelIds);
List<SubjectLabelBO> subjectLabelBOList = SubjectLabelBOConverter.INSTANCE.LabelListToLabelBoList(subjectLabelList);
}
}
return subjectCategoryBOList;
}
二、自定义线程工厂
在构造函数中引入name,使得日志信息更加清晰
package com.ssm.subject.domain.config;
import org.apache.commons.lang.StringUtils;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 自定义名称的线程工厂
*/
public class CustomNameThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix; //线程池名称前缀
//构造函数处传入线程池名称
CustomNameThreadFactory(String name) {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
if(StringUtils.isBlank(name)) {
name = "pool";
}
namePrefix = name + "-" +
poolNumber.getAndIncrement() +
"-thread-";
}
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
三、自定义线程池
@Configuration
public class ThreadPoolConfig {
@Bean(name = "labelThreadPool")
public ThreadPoolExecutor getLabelThreadPool() {
return new ThreadPoolExecutor(20, 100, 5, TimeUnit.SECONDS,
new LinkedBlockingDeque<>(40),
// Executors.defaultThreadFactory(),
new CustomNameThreadFactory("label"),
new ThreadPoolExecutor.CallerRunsPolicy());
}
}
构造方法:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
参数解读:
-
corePoolSize 核心线程数目 (最多保留的线程数)
-
maximumPoolSize 最大线程数目
-
keepAliveTime 生存时间 - 针对救急线程
-
unit 时间单位 - 针对救急线程
-
workQueue 阻塞队列
-
threadFactory 线程工厂 - 可以为线程创建时起个好名字
-
handler 拒绝策略
工作原理:
-
线程池中刚开始没有线程,当一个任务提交给线程池后,线程池会创建一个新线程来执行任务。
-
当线程数达到 corePoolSize 并没有线程空闲,这时再加入任务,新加的任务会被加入workQueue 队列排队,直到有空闲的线程。
-
如果队列选择了有界队列,那么任务超过了队列大小时,会创建 maximumPoolSize - corePoolSize 数目的线程来救急。
-
如果线程到达 maximumPoolSize 仍然有新任务这时会执行拒绝策略。拒绝策略 jdk 提供了 4 种实现,其它著名框架也提供了实现
- AbortPolicy 让调用者抛出 RejectedExecutionException 异常,这是默认策略
- CallerRunsPolicy 让调用者运行任务
- DiscardPolicy 放弃本次任务
- DiscardOldestPolicy 放弃队列中最早的任务,本任务取而代之
- Dubbo 的实现,在抛出 RejectedExecutionException 异常之前会记录日志,并 dump 线程栈信息,方便定位问题
- Netty 的实现,是创建一个新线程来执行任务
- ActiveMQ 的实现,带超时等待(60s)尝试放入队列,类似我们之前自定义的拒绝策略
- PinPoint 的实现,它使用了一个拒绝策略链,会逐一尝试策略链中每种拒绝策略
-
当高峰过去后,超过corePoolSize 的救急线程如果一段时间没有任务做,需要结束节省资源,这个时间由keepAliveTime 和 unit 来控制。
四、多线程优化分类查询
4.1 抽取获取标签集合的方法
在原有线程中,根据每一个小分类集合进行foreach遍历,来进行查询标签集合的,这样在小分类对象有序查询的条件下,可使得标签集合与分类对象对应上。
而在多线程中,需要令所有的分类对象同步查询标签集合,有的线程运行快,有的慢,无法控制线程执行的顺序,所以标签集合无法得知属于哪个分类。
此时采用了Map集合映射的方式,key:分类id,value:为标签集合,就可顺利拒绝上述问题。
为了使代码更加整洁,先要抽取获取标签集合的方法,返回值类型为Map,key可在入参对象中获取
private Map<Long, List<SubjectLabelBO>> getLabelBoList(SubjectCategoryBO bo) {
Map<Long, List<SubjectLabelBO>> labelMap = new HashMap<>();
SubjectMapping subjectMapping = new SubjectMapping();
subjectMapping.setCategoryId(bo.getId());
//获取 题目_标签_分类 的实体对象
List<SubjectMapping> subjectMappingList = subjectMappingService.queryByLabelId(subjectMapping);
if (CollectionUtils.isEmpty(subjectMappingList)) {
return null;
}
//根据标签id获取标签对象
List<Long> labelIds = subjectMappingList.stream().map(SubjectMapping::getLabelId).collect(Collectors.toList());
List<SubjectLabel> subjectLabelList = subjectLabelService.queryByLabelIds(labelIds);
List<SubjectLabelBO> subjectLabelBOList = SubjectLabelBOConverter.INSTANCE.LabelListToLabelBoList(subjectLabelList);
labelMap.put(bo.getId(), subjectLabelBOList);
return labelMap;
}
4.2 futuretask
@Override
@SneakyThrows
public List<SubjectCategoryBO> queryCategoryAndLabel(SubjectCategoryBO subjectCategoryBO) {
if(log.isInfoEnabled()) {
log.info("SubjectCategoryDomainService.queryCategoryAndLabel.bo:{}", JSON.toJSONString(subjectCategoryBO));
}
//查询大分类下的小分类
SubjectCategory subjectCategory = new SubjectCategory(); //bo中代入了id,此处不使用convert转化
subjectCategory.setParentId(subjectCategoryBO.getId());
subjectCategory.setCategoryType(CategoryTypeEnum.SECOND.getCode());
subjectCategory.setIsDeleted(IsDeletedFlagEnum.UN_DELETED.getCode());
List<SubjectCategory> subjectCategoryList = subjectCategoryService.queryCategory(subjectCategory);
List<SubjectCategoryBO> subjectCategoryBOList = SubjectCategoryBOConverter.INSTANCE.categoryListToBOList(subjectCategoryList);
//一次获取标签信息 -- 线程池并发调用
if(!CollectionUtils.isEmpty(subjectCategoryBOList)) {
//此时进行多线程查询,有多个线程任务同时运行,使用List存储
List<FutureTask<Map<Long, List<SubjectLabelBO>>>> futureTaskList = new LinkedList<>();
subjectCategoryBOList.forEach(category -> {
FutureTask<Map<Long, List<SubjectLabelBO>>> futureTask = new FutureTask<>(() -> getLabelBoList(category));
futureTaskList.add(futureTask);
//FutureTask为任务对象,使用线程池进行提交执行
labelThreadPool.submit(futureTask);
});
Map<Long, List<SubjectLabelBO>> map = new HashMap<>();
for(FutureTask<Map<Long, List<SubjectLabelBO>>> futureTask : futureTaskList) {
//主线程阻塞等待异步线程执行完毕,获取返回结果
Map<Long, List<SubjectLabelBO>> resultMap = futureTask.get();
if(CollectionUtils.isEmpty(resultMap)) continue;
map.putAll(resultMap); //putAll把一个map的值存到另一个map
}
//最后进行labelBoList的组装
subjectCategoryBOList.forEach(categoty -> categoty.setLabelBOList(map.get(categoty.getId())));
}
return subjectCategoryBOList;
}
4.3 completableFuture
@Override
@SneakyThrows
public List<SubjectCategoryBO> queryCategoryAndLabel(SubjectCategoryBO subjectCategoryBO) {
if(log.isInfoEnabled()) {
log.info("SubjectCategoryDomainService.queryCategoryAndLabel.bo:{}", JSON.toJSONString(subjectCategoryBO));
}
//查询大分类下的小分类
SubjectCategory subjectCategory = new SubjectCategory(); //bo中代入了id,此处不使用convert转化
subjectCategory.setParentId(subjectCategoryBO.getId());
subjectCategory.setCategoryType(CategoryTypeEnum.SECOND.getCode());
subjectCategory.setIsDeleted(IsDeletedFlagEnum.UN_DELETED.getCode());
List<SubjectCategory> subjectCategoryList = subjectCategoryService.queryCategory(subjectCategory);
List<SubjectCategoryBO> subjectCategoryBOList = SubjectCategoryBOConverter.INSTANCE.categoryListToBOList(subjectCategoryList);
//一次获取标签信息 -- 线程池并发调用
if(!CollectionUtils.isEmpty(subjectCategoryBOList)) {
/**
* CompletableFuture.supplyAsync创建带有返回值的异步任务,并向线程池提交
*/
List<CompletableFuture<Map<Long, List<SubjectLabelBO>>>> completableFutureList = subjectCategoryBOList.stream()
.map(category -> CompletableFuture.supplyAsync(() -> getLabelBoList(category), labelThreadPool))
.collect(Collectors.toList());
//主线程阻塞等待异步线程执行完毕,获取返回结果
completableFutureList.forEach(future -> {
try {
Map<Long, List<SubjectLabelBO>> map1 = future.get();
map.putAll(map1);
} catch (Exception e) {
e.printStackTrace();
}
});
//最后进行labelBoList的组装
subjectCategoryBOList.forEach(categoty -> categoty.setLabelBOList(map.get(categoty.getId())));
}
return subjectCategoryBOList;
}