多线程优化分类查询

30 阅读5分钟

一、前言

在原有的一次性查询分类及标签的方法中,采用的为先查询小分类对象,对小分类集合进行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;
    }