使用forkjoin多线程执行任务,然后汇总结果


前言

改造前:原项目通过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。因为多线程会涉及到上下文的切换,所以数据量不大的时候使用串行比使用多线程快;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值