前言
改造前:原项目通过socket对外提供服务,使用线程池接收各个服务的请求,然后生成回单pdf,返回base64格式的长字符串,每个回单通过换行符隔开,单次请求至多100个。现在为每个请求单线程生成回单pdf。
改在后:线程池接收到请求后,使用forkjoin多线程生成回单,将请求fork成多个子任务,然后将bese64格式的回单join在一起。
一、Fork/Join
Fork/Join是一个分而治之的任务框架,如一个任务需要多线程执行,分割成很多块计算的时候,可以采用这种方法。
二、使用场景
1、Fork/Join框架适合能够进行拆分再合并的计算密集型(CPU密集型)任务。
2、ForkJoin框架是一个并行框架,因此要求服务器拥有多CPU、多核,用以提高计算能力。
三、具体代码
1.测试案例
代码如下:
public class CountTask extends RecursiveTask<Integer> {
private static final int THRESHOLD = 2; //阀值
private int start;
private int end;
public CountTask(int start,int end){
this.start = start;
this.end = end;
}
@Override
protected Integer compute() {
int sum = 0;
boolean canCompute = (end - start) <= THRESHOLD;
if(canCompute){
for(int i = start; i <= end; i++){
sum += i;
}
}else{
int middle = (start + end) / 2;
CountTask leftTask = new CountTask(start,middle);
CountTask rightTask = new CountTask(middle + 1,end);
//执行子任务
leftTask.fork();
rightTask.fork();
//等待子任务执行完,并得到其结果
Integer rightResult = rightTask.join();
Integer leftResult = leftTask.join();
//合并子任务
sum = leftResult + rightResult;
}
return sum;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
ForkJoinPool forkJoinPool = new ForkJoinPool();
CountTask countTask = new CountTask(1,200);
ForkJoinTask<Integer> forkJoinTask = forkJoinPool.submit(countTask);
System.out.println(forkJoinTask.get());
}
}
2.实际案例
代码如下(示例):
package com.hime.aoto.project.action.impl;
import com.hime.aoto.common.CreateHdPdf;
import com.hime.aoto.common.config.HimeConfig;
import com.hime.aoto.common.utils.Base64Util;
import com.hime.aoto.common.utils.DateUtils;
import com.hime.aoto.common.utils.StringUtils;
import com.hime.aoto.framework.manager.AsyncManager;
import com.hime.aoto.framework.manager.factory.AsyncFactory;
import com.hime.aoto.model.ResultModel;
import com.hime.aoto.project.entity.BillFlowGeneral;
import com.hime.aoto.project.service.BillFlowGeneralService;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author: lijx
* @date: 2022/9/28 15:15
* @description: 回单下载 HTB110
*/
@Slf4j
@Component
public class HdDownload {
@Autowired
private BillFlowGeneralService billFlowGeneralService;
public ResultModel dealReq(Map<String, Object> reqMap) {
List<BillFlowGeneral> successList = Collections.synchronizedList(new ArrayList<>());
List<Integer> innerNos = Collections.synchronizedList(new ArrayList<>());
String printDateTime = DateUtils.getTime();
Map<String, Object> dataMap = new HashMap<>();
//有效回单数量
AtomicInteger billCount = new AtomicInteger(0);
List<String> detailFile = (List<String>) reqMap.get("DetailFile");
ForkJoinTask<String> task = new RespTask(billFlowGeneralService, reqMap.get("OperNo").toString(),
reqMap.get("SendNode").toString(),printDateTime,innerNos,successList,billCount,detailFile);
long startTime = System.currentTimeMillis ();
String invoke = ForkJoinPool.commonPool().invoke(task);
long endTime = System.currentTimeMillis();
log.info("共耗时{}ms", (endTime-startTime));
dataMap.put("DetailFile", invoke);
return ResultModel.success(dataMap);
}
}
class RespTask extends RecursiveTask<String> {
private BillFlowGeneralService billFlowGeneralService;
//操作员
private String operNo;
///打印机构
private String sendNode;
//打印时间
private String printDateTime;
//打印成功的内部编号
private List<Integer> innerNos;
//打印成功的回单信息
List<BillFlowGeneral> successList;
//用于统计打印成功的回单数量
private AtomicInteger billCount;
//请求信息
private List<String> detailFile;
public RespTask(BillFlowGeneralService billFlowGeneralService, String operNo, String sendNode, String printDateTime,
List<Integer> innerNos, List<BillFlowGeneral> successList, AtomicInteger billCount, List<String> detailFile) {
this.billFlowGeneralService = billFlowGeneralService;
this.operNo = operNo;
this.sendNode = sendNode;
this.printDateTime = printDateTime;
this.innerNos = innerNos;
this.successList = successList;
this.billCount = billCount;
this.detailFile = detailFile;
}
@SneakyThrows
@Override
protected String compute() {
StringBuffer respDetailFile = new StringBuffer();
if(detailFile.size() <= 5){
for (String s : detailFile) {
String[] detailFiles = s.split("\\|");
//文本序号
String sqlNum = detailFiles[0];
//中心账号
String saAcctNo = detailFiles[1];
//主机流水号
String saTxLogNo = detailFiles[2];
//交易日期
String saTxDt = detailFiles[3];
respDetailFile.append(sqlNum).append("|").append(saAcctNo).append("|").
append(saTxLogNo).append("|").append(saTxDt).append("|");
BillFlowGeneral billFlowGeneral = new BillFlowGeneral();
billFlowGeneral.setYearDate(saTxDt);
billFlowGeneral.setSaAcctNo(saAcctNo);
billFlowGeneral.setSaTxLogNo(saTxLogNo);
billFlowGeneral.setSaTxDt(saTxDt);
Integer count = billFlowGeneralService.queryCount(billFlowGeneral);
if (0 != count) {
billCount.getAndAdd(1);
String fileName = String.format("%s-%s-%s-%s.pdf", sqlNum, saAcctNo, saTxLogNo, saTxDt);
BillFlowGeneral billFlowGeneralResult = billFlowGeneralService.queryBillFlowGeneral(billFlowGeneral);
//生成pdf
CreateHdPdf.createPdfHd(billFlowGeneralResult, fileName, operNo, sendNode, printDateTime);
String base64Str = Base64Util.PDFToBase64(HimeConfig.getHdPdfPath() + DateUtils.dateTime() + "/" + fileName);
respDetailFile.append("1").append("|").append(base64Str).append("|\n");
innerNos.add(billFlowGeneralResult.getInnerNo());
successList.add(billFlowGeneralResult);
} else {
respDetailFile.append("2").append("|").append(" |\n");
}
}
return respDetailFile.toString();
}
//任务太大,一分为二
List<String> list1 = detailFile.subList(0, detailFile.size() / 2);
List<String> list2 = detailFile.subList(detailFile.size() / 2, detailFile.size());
RespTask respTask1 = new RespTask(billFlowGeneralService, operNo,sendNode,printDateTime,innerNos,successList,billCount,list1);
RespTask respTask2 = new RespTask(billFlowGeneralService, operNo,sendNode,printDateTime,innerNos,successList,billCount,list2);
invokeAll(respTask2,respTask1);
String join1 = respTask1.join();
String join2 = respTask2.join();
return join1+join2;
}
}
总结
对于fork/join来说,在使用时还是存在下面的一些问题的:
在使用JVM的时候我们要考虑OOM的问题,如果我们的任务处理时间非常耗时,并且处理的数据非常大的时候,会造成OOM;
ForkJoin是通过多线程的方式进行处理任务,那么我们不得不考虑是否应该使用ForkJoin。因为当数据量不是特别大的时候,我们没有必要使用ForkJoin。因为多线程会涉及到上下文的切换,所以数据量不大的时候使用串行比使用多线程快;