多线程分割处理数据完美方案

介绍

在项目中我们经常要处理一些大数据量的数据,譬如有100万的数据处理后进行入库,当然我们可以用springBatch框架,但是大部分情况下我们可能只需要开启多线程处理就行了,之前每次遇到新项目都是重写,或者把之前的代码改下很麻烦,而且网上给的工具类大部分不能返回成功条数,失败条数失败的原始数据和失败的堆栈信息返回结果。满足不了我的需求,因此自己写了一个工具类。
注意事项
因为我自己写的工具类返回的信息比较详细,如果数据量大而且报错信息又很多话,有可能OOM,自己只要改下HandleSupplier类errorData.setErrorException(ExceptionUtil.stacktraceToString(ex))代码即可。
有啥问题欢迎评论,我会第一时间回复大家,也可以私信我。加我微信

使用方式

使用方式很简单只需要两步
第一:实现 ITask 接口
第二:调用 multiThreadUtil的execute() 方法

public interface ITask<E> {
  /**
   * 任务执行方法接口<BR>
   * 方法名:execute<BR>
   * @param e 传入对象
   * @param params 其他辅助参数
   * @return T<BR> 返回值类型
   */
  ResultSingleBean execute(E e, Map<String, Object> params);
}
 /**
     * @param data        数据
     * @param params      扩展参数
     * @param task        任务实现类
     * @param executor    线程池可以为空
     * @param threadCount 线程个数 默认10
     */
    public  ResultBean execute(List<T> data, Map<String, Object> params, ITask<T> task, Executor executor, Integer threadCount)

例子

@Data
public class ListDataTest implements ITask<String> {

    @Override
    public ResultSingleBean execute(String str, Map params) {
        ResultSingleBean resultSingleBean = new ResultSingleBean();
        String ss = str.replace("小明", "");
        Integer integer = Integer.valueOf(ss);
        if (integer % 2 == 0) {
            throw  new RuntimeException("错误了");
        }
        UserBean userBean = new UserBean();
        userBean.setName(str);
        resultSingleBean.setData(userBean);
        resultSingleBean.setSuccess(true);
        return resultSingleBean;
    }

    public static void main(String[] args) {
        ListDataTest listDataTest = new ListDataTest();
        List<String> data = new ArrayList<String>(33);
        for (int i = 0; i < 34; i++) {
            data.add("小明" + i);
        }
        // 辅助参数  加数
        Map<String, Object> params = new HashMap<>();
        params.put("addNum", 4);

        MultiThreadUtil multiThreadUtil = new MultiThreadUtil();
        ResultBean<UserBean> execute = multiThreadUtil.execute(data, params, listDataTest, null, 3);
        System.out.println("====================================");
        System.out.println(JSON.toJSONString(execute));
    }
}

返回结果
在这里插入图片描述

具体代码

保存失败信息实体类

/**
 * @Package: com.example.thread.task
 * @ClassName: ErrorData
 * @Author: xu kang kai
 * @Description:
 * @Date: 2022/12/7 15:42
 * @Version: 1.0
 */
@Data
public class ErrorData {
    //原始数据
    private String origData;

    // 处理失败原因

    private String errorException;

}

线程实现类这里实现的是Supplier 类似于Runabble。

package com.example.thread.task;

import cn.hutool.core.exceptions.ExceptionUtil;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.ObjectUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;

/**
 * @Package: com.example.thread.task
 * @ClassName: HandleSupplier
 * @Author: xu kang kai
 * @Description:
 * @Date: 2022/12/7 15:35
 * @Version: 1.0
 */
@Slf4j
public class HandleSupplier<E> implements Supplier<ResultBean> {
    // 线程名称
    private String threadName = "";

    // 需要处理的数据
    private List<E> data;

    // 辅助参数
    private Map<String, Object> params;

    // 具体执行任务
    private ITask<E> task;


    public HandleSupplier(String threadName, List<E> data, Map<String, Object> params,
                          ITask<E> task) {
        this.threadName = threadName;
        this.data = data;
        this.params = params;
        this.task = task;
    }


    @Override
    public ResultBean get() {
        // 该线程中所有数据处理返回结果
        ResultBean resultBean = new ResultBean();
        if (!ObjectUtils.isEmpty(data)) {
            log.info("线程:{},共处理:{}个数据,开始处理......", threadName, data.size());
            Integer succes = 0;
            Integer error = 0;
            List<Object> list = new ArrayList<>();
            List<ErrorData> errorDataList = new ArrayList<>();
            // 循环处理每个数据
            for (int i = 0; i < data.size(); i++) {
                // 需要执行的数据
                E e = data.get(i);
                // 将数据执行结果加入到结果集中
                ResultSingleBean<Object> execute = null;
                try {
                    execute = task.execute(e, params);
                } catch (Exception ex) {
                    log.error("执行任务失败", ex);
                    execute = new ResultSingleBean<>();
                    execute.setSuccess(false);
                    ErrorData errorData = new ErrorData();
                    errorData.setOrigData(JSON.toJSONString(e));
                    errorData.setErrorException(ExceptionUtil.stacktraceToString(ex));
                    execute.setErrorData(errorData);
                }
                if (execute.isSuccess) {
                    succes = succes + 1;
                } else {
                    error = error + 1;
                }
                if (null != execute.getErrorData()) {
                    errorDataList.add(execute.getErrorData());
                }
                if (!ObjectUtils.isEmpty(execute.getData())) {
                    list.add(execute.getData());
                }
            }
            resultBean.setErrorNum(error);
            resultBean.setSuccessNum(succes);
            resultBean.setErrorDataList(errorDataList);
            resultBean.setLists(list);
        }
        return resultBean;
    }
}

任务接口类

package com.example.thread.task;
import java.util.Map;
/**
 * 任务处理接口
 * 具体业务逻辑可实现该接口
 *  T 返回值类型
 *  E 传入值类型
 */
public interface ITask<E> {
    /**
     * 任务执行方法接口<BR>
     * 方法名:execute<BR>
     * @param e 传入对象
     * @param params 其他辅助参数
     * @return T<BR> 返回值类型
     */
    ResultSingleBean execute(E e, Map<String, Object> params);
}

多线程工具实现类

package com.example.thread.task;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ObjectUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;

/**
 * @ClassName: MultiThreadUtils.java
 * @Description: 多线程工具类
 */
public class MultiThreadUtil<T> {
    private static Logger logger = LoggerFactory.getLogger(MultiThreadUtil.class);


    /**
     * @param data        数据
     * @param params      扩展参数
     * @param task        任务实现类
     * @param executor    线程池可以为空
     * @param threadCount 线程个数 默认10
     */
    public  ResultBean execute(List<T> data, Map<String, Object> params, ITask<T> task, Executor executor, Integer threadCount) {
        ResultBean resultBean = new ResultBean<>();
        resultBean.setLists(new ArrayList());
        resultBean.setErrorDataList(new ArrayList<>());
        resultBean.setErrorNum(0);
        resultBean.setSuccessNum(0);
        if (null == threadCount) {
            threadCount = 10;
        }
        // 开始时间(ms)
        long l = System.currentTimeMillis();
        // 数据量大小
        int length = data.size();
        // 每个线程处理的数据个数
        int taskCount = length / threadCount;

        List<CompletableFuture<ResultBean>> list = new ArrayList<>();
        // 划分每个线程调用的数据
        for (int i = 0; i < threadCount; i++) {
            // 每个线程任务数据list
            List<T> subData = null;
            if (i == (threadCount - 1)) {
                subData = data.subList(i * taskCount, length);
            } else {
                subData = data.subList(i * taskCount, (i + 1) * taskCount);
            }
            // 将数据分配给各个线程
            HandleSupplier handleSupplier = new HandleSupplier<T>(String.valueOf(i), subData, params, task);
            CompletableFuture<ResultBean> objectCompletableFuture = null;
            if (null == executor) {
                objectCompletableFuture = CompletableFuture.supplyAsync(handleSupplier);
            } else {
                objectCompletableFuture = CompletableFuture.supplyAsync(handleSupplier,executor);
            }
            list.add(objectCompletableFuture);
        }
        //为了获取所有线程执行完的结果 用CountDownLatch 和下面的 CompletableFuture.allOf.都是可以的join()
        CompletableFuture<Void> allCF = CompletableFuture.allOf(list.toArray(new CompletableFuture[0]));
        //阻塞,直到所有任务结束。任务complete就会执行, handler里面不一定会执行..
        allCF.join();
        for (CompletableFuture<ResultBean> resultBeanCompletableFuture : list) {
            try {
                ResultBean reb = resultBeanCompletableFuture.get();
                if (!ObjectUtils.isEmpty(reb.getSuccessNum())) {
                    resultBean.setSuccessNum(resultBean.getSuccessNum() + reb.getSuccessNum());
                }
                if (!ObjectUtils.isEmpty(reb.getErrorNum())) {
                    resultBean.setErrorNum(resultBean.getErrorNum() + reb.getErrorNum());
                }
                if (!ObjectUtils.isEmpty(reb.getLists())) {
                    boolean b = resultBean.getLists().addAll(reb.getLists());
                    resultBean.setLists(resultBean.getLists());
                }
                if (!ObjectUtils.isEmpty(reb.getErrorDataList())) {
                    boolean b = resultBean.getErrorDataList().addAll(reb.getErrorDataList());
                    resultBean.setErrorDataList(resultBean.getErrorDataList());
                }
            } catch (Exception e) {
                logger.error("获取线程执行结果异常", e);
            }
        }
        // 执行结束时间
        long end_l = System.currentTimeMillis();
        logger.info("总耗时:{}ms", (end_l - l));
        return resultBean;
    }


}
package com.example.thread.task;

import lombok.Data;

import java.util.List;

/**
 * @Package: com.example.thread.task
 * @ClassName: ResultBean
 * @Author: xu kang kai
 * @Description:
 * @Date: 2022/12/7 15:52
 * @Version: 1.0
 */
@Data
public class ResultBean<T> {

    //成功条数
    private Integer successNum;
    //失败条数
    private Integer errorNum;
    // 结果集
    private List<T> lists;

    // 失败数据返回结果集
    public List<ErrorData> errorDataList;
}
package com.example.thread.task;

import lombok.Data;


/**
 * @Package: com.example.thread.task
 * @ClassName: ResultSingleBean
 * @Author: xu kang kai
 * @Description: 处理每个任务返回结果
 * @Date: 2022/12/7 15:40
 * @Version: 1.0
 */

@Data
public class ResultSingleBean<T> {
    // 是否成功
    public boolean isSuccess = true;

    // 需要返回数据
    private T data;

    // 失败数据返回结果集
    public ErrorData errorData;
}

这个类不需要如果你要跑我上面的例子的话可以用下


import lombok.Data;

/**
 * @Package: com.example.thread.task
 * @ClassName: UserBean
 * @Author: xu kang kai
 * @Description:
 * @Date: 2022/12/8 10:55
 * @Version: 1.0
 */
@Data
public class UserBean {
    private String name;
}
  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值