需求
Excel文件导出场景,当导出的记录数量过多,需要将查询结果集中的部分字段转换为字符串,在转换过程中需要遍历所有结果集操作,导致导出时间超时。
解决思路
1、根据查询结果的数量,先设置计划创建线程数;
2、根据数据量和线程数,将数据切片,切片数据长度=总数/线程处理数;
//处理线程数
int threadNum = allListSize > 1000 ? 8 : 1;
//数据拆分段
int subListSize = (int) Math.ceil(allListSize/ (double) threadNum);
3、初始化保护性暂停的结果收集类。
//保护性暂停的结果收集通信类初始化
GuardedExportExcelObject<ResultObject> guardedObject = new GuardedExportExcelObject<>(threadNum);
4、循环线程数,截取集合
for (int i = 0; i < threadNum; i++) {
//开始下标
int fromIndex = i * subListSize;
//开始下标大于最大下标值
if (fromIndex > allListSize) {
continue;
}
//截取结束的下标
int toIndex = fromIndex + subListSize;
//截取数据
int nextIndex = toIndex < allListSize ? toIndex : allListSize;
List<Object> subList = allList.subList(fromIndex, nextIndex);
//开启线程处理--线程池工具类,可以直接创建线程也行
ThreadPoolExecutor poolExecutor = threadPoolUtil.getThreadPoolByDefault();
poolExecutor.execute(() -> {
log.info("线程执行转换开始时间:{}", LocalDateTime.now());
//个人业务执行方法--替换即可****
List<ResultObject> excelVOS = this.convertExcelVO(serialNumber, subList);
guardedObject.setResultList(excelVOS);
log.info("线程执行转换结束时间:{}", LocalDateTime.now());
});
}
//主线程过去结果方法,线程未执行完, 则会wait()等待。
return guardedObject.getResultList();
结果收集类
package com.chcontrol.core.service.warnmanagement;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author Mr_Fei
* @description 保护性暂停设计模式 结果收集类
* @date 2020-08-28 10:55
*/
@Slf4j
public class GuardedExportExcelObject<T> {
/**
* 线程数 默认1
*/
private int threadNumber = 1;
/**
* 当前完成的线程数原子
*/
private AtomicInteger currentThreadNum = new AtomicInteger(0);
private GuardedExportExcelObject() {
}
public GuardedExportExcelObject(Integer threadNumber) {
this.threadNumber = threadNumber;
}
/**
* 结果集
*/
private List<T> resultList = new ArrayList<>();
/**
* get方法,等待线程结果
*
* @return java.util.List<com.chcontrol.core.model.vo.EquipmentWarnExcelZHVO>
* @author Mr_Fei
* @date 2020/8/28 11:06
* @description getResultList
*/
public List<T> getResultList() {
synchronized (this) {
while (currentThreadNum.get() != threadNumber) {
try {
this.wait();
} catch (InterruptedException e) {
log.error("等待错误,{}", e);
}
}
return this.resultList;
}
}
/**
* 完成线程数+1,并合并结果集
*
* @param resultList 单线程的结果集
* @author Mr_Fei
* @date 2020/8/28 11:07
* @description setResultList
*/
public void setResultList(List<T> resultList) {
synchronized (this) {
this.resultList.addAll(resultList);
//当前完成线程数+1;
this.currentThreadNum.getAndIncrement();
if (currentThreadNum.get() == threadNumber) {
this.notifyAll();
}
}
}
}
线程池工具类
package com.chcontrol.sysutil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 线程池
*
* @author ff
* @date 9:56 2019/12/31
**/
@Component
public class ThreadPoolUtil {
/**
* 核心线程数
*/
@Value("${common.core_pool_size}")
private int commonTPCorePoolSize;
/**
* 最大线程数
*/
@Value("${common.maximum_pool_size}")
private int commonTPMaximumPoolSize;
/**
* 线程闲置等待时间
*/
@Value("${common.keep_alive_time}")
private int commonTPKeepAliveTime;
/**
* 线程优先级 1-4低优先级,5默认,6-10高优先级 max10,min1
*/
@Value("${common.priority}")
private int commonTPPriority;
/**
* 公用线程池
*/
public static final String COMMON_THREAD_POOL = "commonThread";
/**
* 用来装线程池
*/
private Map<String, ThreadPoolExecutor> threadPoolFactory = new HashMap<>();
/**
* @author Mr_Fei
* @date 2020/4/8 10:13
* @description initThreadPool
* 被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次。
* PostConstruct在构造函数之后执行,init()方法之前执行。
* PreDestroy()方法在destroy()方法执行之后执行
*/
@PostConstruct
private void initThreadPool() {
// 核心线程5,最大线程20,线程空闲等待时间10秒,
//有界队列512,任务队列占满时拒绝策略:抛出RejectedExecutionException,
// 线程工厂指定线程优先级和线程名称
//创建公共线程池
ThreadPoolExecutor commonThreadPool = new ThreadPoolExecutor(commonTPCorePoolSize,
commonTPMaximumPoolSize, commonTPKeepAliveTime,
TimeUnit.SECONDS, new ArrayBlockingQueue<>(512),
Thread::new, new ThreadPoolExecutor.AbortPolicy());
threadPoolFactory.put(COMMON_THREAD_POOL, commonThreadPool);
}
/**
* 根据不同的消息类型,得到不同的线程池
*
* @param keyType key类型
* @return java.util.concurrent.ThreadPoolExecutor
* @author zhangyy
*/
public ThreadPoolExecutor getThreadPoolByType(String keyType) {
return threadPoolFactory.get(keyType);
}
/**
* @return java.util.concurrent.ThreadPoolExecutor
* @author Mr_Fei
* @date 2020/4/8 16:46
* @description 获取默认的线程池
*/
public ThreadPoolExecutor getThreadPoolByDefault() {
return threadPoolFactory.get(WORK_ORDER_PLAN_NAME);
}
}