嗯,这确实是一个比较经典的话题。以前听到多线程基本上就已经吓得尿裤子了,但是! 敲黑板了啊 画重点了啊。就在这个月我自己动手写出来了人生中第一个多线程的案例,并且完美解决了业务需要问题。将几百万的数据从一个表取出来 经过处理和再添加到另个表中 。那么我们来撩一撩这个业务的前世今生!
之前已经写过了定时任务的数据压缩 但是那只是压缩一天的,那么从最开始上线到现在会有几个月的数据没有进行压缩,因此最开始我想的是将之前所有天数的数据循环进行压缩,但是这又有一个问题,压缩每一天的数据都会需要几十秒的时间,我要同时压缩几个月的,那将是一段很漫长的过程。所以这个时候,我想到了用多线程的方式执行压缩任务。开10个线程分别去压缩一天任务,然后通过循环将天数进行累加,交给多线程去执行,最后三个月的压缩完毕之后,仅仅用了30秒不到。
上代码:
@Service
@Transactional
public class TenminuteTaskDataCompressionService {
private final Logger log = LoggerFactory.getLogger(TenminuteTaskDataCompressionService.class);
private final TenminuteTaskDataCompressionRepository tenminuteTaskDataCompressionRepository;
@Autowired
TenminuteTaskRepository tenminuteTaskRepository;
private ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 20, 2000, TimeUnit.MILLISECONDS,
new LinkedBlockingDeque(1000));
public TenminuteTaskDataCompressionService(TenminuteTaskDataCompressionRepository tenminuteTaskDataCompressionRepository) {
this.tenminuteTaskDataCompressionRepository = tenminuteTaskDataCompressionRepository;
}
public int excuteDataCompressionByOperateDate(String operateDate,String deleteFlag) {
log.debug("the request param is :{}",operateDate,deleteFlag);
//传1异常压缩 先删压缩表当天数据再压缩
if("1".equals(deleteFlag)){
tenminuteTaskDataCompressionRepository.deleteAllByOperateDate(operateDate);
}
// 1.取出前一天的所有数据
List allByOprateDate = tenminuteTaskRepository.findAllByOprateDate(operateDate);
log.debug("总共的数据条数为"+allByOprateDate.size());
if(allByOprateDate.isEmpty() && allByOprateDate.size() == 0){
return 0;
}
List compressionList = null;
try {
log.debug("开始压缩数据,目标天数为:{}",operateDate);
// 2.封装数据
compressionList = createCompressionList(allByOprateDate);
// 3.将压缩的数据 存到压缩表
tenminuteTaskDataCompressionRepository.save(compressionList);
log.debug("已将数据存入压缩表,条数为"+compressionList.size());
// 4.删除原表指定天数数据
tenminuteTaskRepository.deleteAllByOperateDate(operateDate);
}catch (Exception e){
log.error("压缩数据发生异常{}",e.getMessage());
}
return 1;
}
/**
* 将前一天的数据进行转化为新的实体集合
*
* @param allByOprateDate
* @return
*/
public List createCompressionList(List allByOprateDate) {
Map> collect = allByOprateDate.stream().collect(Collectors.groupingBy(TenminuteTask::getDeviceUuid));
List listvo = Lists.newArrayList();
collect.forEach((k, v) -> {
TenminuteTaskDataCompression allentity = new TenminuteTaskDataCompression();
allentity.setUuid(UUIDUtil.getAscId());
allentity.setDeviceUuid(k);
allentity.setTenantUuid(v.get(0).getTenantUuid());
allentity.setOperateDate(v.get(0).getOperateDate());
allentity.setCreatedBy("system");
allentity.setLastModifiedBy("system");
Map> integerListMap = v.stream().collect(Collectors.groupingBy(TenminuteTask::getOperateHour));
Map> stringMapList= new HashMap<>();
integerListMap.forEach((hour,tenminuteTasks)->{
// 将这个list转换成string类型的数组集合
List stringList = new ArrayList<>();
// 将集合数据转成数组
tenminuteTasks.forEach(tenminuteTask -> {
String[] aa= new String[12];
//下标为0 存在线时间
aa[0]=tenminuteTask.getOnTime().toString();
//下标为1 存作业时间
aa[1]=tenminuteTask.getOperationTime().toString();
//下标为2 存待机时间
aa[2]=tenminuteTask.getStandByTime().toString();
//下标为3 存停机时间
aa[3]=tenminuteTask.getDownTime().toString();
//下标为4 存离线时间
aa[4]=tenminuteTask.getOfflineTie().toString();
//下标为5 存能耗
aa[5]=tenminuteTask.getEnergy().toString();
//下标为6 存电价
aa[6]=tenminuteTask.getElectPrice().toString();
//下标为7 存成本
aa[7]=tenminuteTask.getCostPrice().toString();
//下标为8 存操作时间
aa[8]=tenminuteTask.getOperateHour().toString();
//下标为9 存操作分钟
aa[9]=tenminuteTask.getOperateMinute().toString();
//下标为10 存最小能耗
aa[10]=tenminuteTask.getMinusEnergy().toString();
//下标为11 存完整时间
aa[11]=tenminuteTask.getOperateFullDate();
stringList.add(aa);
});
stringMapList.put(hour,stringList);
//然后遍历map1的时候 把这些值put到map2中
});
Gson gson = new Gson();
String json = gson.toJson(stringMapList);
allentity.setCompressionJsonInfo(json);
listvo.add(allentity);
});
return listvo;
}
/**
* 批量压缩十分钟数据接口 应该是使用一次 以后的数据由定时任务压缩即可
* @param startDate
* @param endDate
*/
public void excuteBetchDataCompression(String startDate, String endDate) {
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
LocalDate s = LocalDate.parse(startDate);
LocalDate e= LocalDate.parse(endDate);
//相差天数设置成活的
long num=e.toEpochDay()-s.toEpochDay();
int number=(int) num;
for (int i = 0; i < number; i++) {
if (endDate.equals(startDate)) {
break;
}
executor.execute(new TenminuteCompressionTask(i, startDate,this));
LocalDate parse = LocalDate.parse(startDate);
startDate = parse.plusDays(1).format(dateTimeFormatter);
}
//显式调用之后 线程池就关闭了 再调用接口的话就无法使用了 得重新启动 如果不显式关闭的话 线程会一直存在 jvm不会退出 直到新的请求进来
//executor.shutdown();
}
class TenminuteCompressionTask implements Runnable {
private int taskNum;
private String startDate;
TenminuteTaskDataCompressionService tenminuteTaskDataCompressionService;
public TenminuteCompressionTask(int taskNum, String startDate,TenminuteTaskDataCompressionService tenminuteTaskDataCompressionService) {
this.taskNum = taskNum;
this.startDate = startDate;
this.tenminuteTaskDataCompressionService =tenminuteTaskDataCompressionService;
}
@Override
public void run() {
log.debug("正在执行task " + taskNum+"当前线程为"+Thread.currentThread().getName()+" 正在压缩" + startDate + "的数据。。。。");
tenminuteTaskDataCompressionService.excuteDataCompressionByOperateDate(startDate,"0");
log.debug(startDate+"的数据压缩完毕");
}
}
}
通过线程池的方式去创建线程,减少创建和销毁线程的资源消耗!