多线程处理List工具类ParallelHandleUtil

说明:本工具类不适用于所有场景,如果你的list集合处理,单次循环处理逻辑耗时久,涉及到大量io操作、远程调用,那么可以尝试使用本工具,但如果list单次循环处理逻辑耗时很短,远远小于使用多线程时线程上下文切换的耗时,那么不应再使用本工具,直接使用普通的for循环更快

一、ParallelHandleUtil

package com.satoken.utils;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.*;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.logging.Logger;
import java.util.stream.Collectors;

/**
 * 以下为使用实列,日常编码如需循环处理大list集合,且单次循环十分耗时且io操作占主要资源(比如循环中文件读取,网络通信),建议使用多线程并行处理,提高处理效率,以下为使用实例:把处理逻辑封装为函数,使用此工具
 * 注意以下示例只展示用法,并不意味着并行处理一定比串行处理效率高,有时候和线程数是否设置合理,上下文切换是否消耗资源,还有其他因素影响处理效率,具体情况具体分析。
 * List<Integer> list = new LinkedList<>();
 * for (int i = 1; i <= 10000000; i++) {
 * list.add(i);
 * System.out.println(i);
 * }
 * 1000000为每个线程处理的数据条数,总数为10000000,线程数目就为10,设置最大线程数为50,如果每个线程处理100000条数据,则需要100个线程,超过50则会重新计算线程数
 * Function<List<Integer>,List<Integer>> function = (List<Integer> handleList)-> handleList.stream().map(loop->loop+1).collect(Collectors.toList());
 * ParallelHandleUtil.functionHandle(1000000,50,list,function);
 * 多线程处理io密集型计算任务(一般不适用cpu密集型操作),不支持事务
 *
 * @author hulei
 * @date 2024/01/22 13:45
 */
public final class ParallelHandleUtil {
    /** jdk自带日志工具 */
    private static final Logger logger = Logger.getLogger(ParallelHandleUtil.class.getName());

    /**
     * 获取线程数
     *
     * @param dataSize   数据集合大小(长度)
     * @param handleSize 每个线程处理list的大小
     * @return int 返回线程数
     */
    private static int getThreadNum(int dataSize, int handleSize) {
        //线程数
        int threadNum;
        //正好整除则线程数为待处理数据大小与线程处理数据大小的商,否则多加一条线程处理多余数据(剩余不足threadSize数据,即余数)
        if (dataSize % handleSize == 0) {
            threadNum = dataSize / handleSize;
        } else {
            threadNum = dataSize / handleSize + 1;
        }
        return threadNum;
    }

    /**
     * 单个线程处理数据条数,list.size()/threadSize<=10,一般建议线程数少于10,不至于线程太多上下文切换浪费资源,出现内存占用比过高
     *
     * @param handleSize   每个线程处理list的大小
     * @param maxThreadNum 最大线程数大小
     * @param list         待处理list集合
     * @param consumer     List<T>的处理逻辑消费型函数无返回值
     */
    public static <T> void consumerHandle(int handleSize, int maxThreadNum, List<T> list, Consumer<List<T>> consumer) {
        int threadNum = getThreadNum(list.size(), handleSize);
        if (threadNum > maxThreadNum) {
            //如果超过最大线程数,则重新设置单线程处理条数,并依据新计算的handleSize,重新计算threadNum
            handleSize = list.size() / maxThreadNum + 1;
            threadNum = getThreadNum(list.size(), handleSize);
        }
        //收集Callable集合,不需要返回值
        List<Callable<Void>> tasks = getTasks(threadNum, handleSize, list, consumer);
        //固定线程池
        ExecutorService executorService = Executors.newFixedThreadPool(threadNum);
        try {
            executorService.invokeAll(tasks);
        } catch (Exception e) {
            logger.severe("批处理失败");
        } finally {
            //关闭线程池
            executorService.shutdown();
        }
    }

    /**
     * @param handleSize   每个线程处理list的大小
     * @param maxThreadNum 最大线程数大小
     * @param list         待处理list集合(入参一)
     * @param u            入参二
     * @param biConsumer   函数式处理逻辑(无返回值)
     * @param <T>          list集合中元素类型
     * @param <U>          入参二的类型
     */
    public static <T, U> void biConsumerHandle(int handleSize, int maxThreadNum, List<T> list, U u, BiConsumer<List<T>, U> biConsumer) {
        int threadNum = getThreadNum(list.size(), handleSize);
        if (threadNum > maxThreadNum) {
            //如果超过最大线程数,则重新设置单线程处理条数,并依据新计算的handleSize,重新计算threadNum
            handleSize = list.size() / maxThreadNum + 1;
            threadNum = getThreadNum(list.size(), handleSize);
        }
        //收集Callable集合,不需要返回值
        List<Callable<Void>> tasks = getTasksTwoParam(threadNum, handleSize, list, u, biConsumer);
        excuteConsumer(threadNum, tasks);
    }

    /**
     * 执行Consumer,BiConsumer任务集合,无返回值
     * @param threadNum 线程数
     * @param tasks 异步任务集合
     */
    private static void excuteConsumer(int threadNum, List<Callable<Void>> tasks) {
        //固定线程池
        ExecutorService executorService = Executors.newFixedThreadPool(threadNum);
        try {
            executorService.invokeAll(tasks);
        } catch (Exception e) {
            logger.severe("批处理失败");
        } finally {
            //关闭线程池
            executorService.shutdown();
        }
    }

    /**
     * 一个参数的Funtion函数
     *
     * @param handleSize   每个线程处理list的大小
     * @param maxThreadNum 最大线程数
     * @param list         待处理list集合(入参一)
     * @param function     List<T>的处理逻辑函数有返回值
     * @param <T>          list集合中元素类型
     * @param <R>          List<T>处理后返回集合的元素类型
     * @return List<R> 返回集合
     */
    public static <T, R> List<R> functionHandle(int handleSize, int maxThreadNum, List<T> list, Function<List<T>, List<R>> function) {
        int threadNum = getThreadNum(list.size(), handleSize);
        if (threadNum > maxThreadNum) {
            //如果超过最大线程数,则重新设置单线程处理条数,并依据新计算的handleSize,重新计算threadNum
            handleSize = list.size() / maxThreadNum + 1;
            threadNum = getThreadNum(list.size(), handleSize);
        }
        List<Callable<List<R>>> tasks = getTasks(threadNum, handleSize, list, function);
        return excuteFunction(threadNum, tasks);
    }

    /**
     * 两个参数的BiFunction逻辑
     *
     * @param handleSize   每个线程处理list的大小
     * @param maxThreadNum 最大线程数
     * @param list         待处理list集合(入参一)
     * @param u            入参二
     * @param biFunction   函数式处理逻辑(有返回值)
     * @param <T>          list集合中元素类型
     * @param <U>          入参二的类型
     * @param <R>          返回list集合中的元素类型
     * @return List<R> 返回集合
     */
    public static <T, U, R> List<R> biFunctionHandle(int handleSize, int maxThreadNum, List<T> list, U u, BiFunction<List<T>, U, List<R>> biFunction) {
        int threadNum = getThreadNum(list.size(), handleSize);
        if (threadNum > maxThreadNum) {
            //如果超过最大线程数,则重新设置单线程处理条数,并依据新计算的handleSize,重新计算threadNum
            handleSize = list.size() / maxThreadNum + 1;
            threadNum = getThreadNum(list.size(), handleSize);
        }
        List<Callable<List<R>>> tasks = getTasksTwoParam(threadNum, handleSize, list, u, biFunction);
        return excuteFunction(threadNum, tasks);
    }

    /**
     * 获取Function,BiFunction函数处理结果
     * @param threadNum 线程数
     * @param tasks     异步任务集合
     * @param <R>       返回list集合中的元素类型
     * @return 返回集合
     */
    private static <R> List<R> excuteFunction(int threadNum, List<Callable<List<R>>> tasks) {
        //固定线程池
        ExecutorService executorService = Executors.newFixedThreadPool(threadNum);
        try {
            List<Future<List<R>>> futureList = executorService.invokeAll(tasks);
            //获取返回结果,扁平化处理,获取到的结果流化
            return futureList.stream().flatMap(item -> {
                try {
                    return item.get().stream();
                } catch (InterruptedException | ExecutionException e) {
                    logger.severe(String.format("扁平化处理获取返回结果失败:%s,%s", e.getMessage(), e));
                }
                return null;
            }).collect(Collectors.toList());
        } catch (Exception e) {
            logger.severe(String.format("批处理失败:%s,%s", e.getMessage(), e));
        } finally {
            //关闭线程池
            executorService.shutdown();
        }
        return Collections.emptyList();
    }

    /**
     * 获取任务集合(单个线程处理的任务集合)
     *
     * @param threadNum    线程数
     * @param handleSize   每个线程处理list的大小
     * @param list         待处理list集合
     * @param objectMethod 处理逻辑函数(Function、Consumer)
     * @param <T>          待处理List集合元素类型
     * @param <V>          Callable的返回值类型,可能为Void,或者具体的类型(Consumer时返回类型Void,Funtion时)
     * @return List<Callable < V>> 返回Callable集合
     */
    private static <T, V> List<Callable<V>> getTasks(int threadNum, int handleSize, List<T> list, Object objectMethod) {
        //收集Callable集合,不需要返回值
        List<Callable<V>> tasks = new ArrayList<>();
        //每个线程的处理任务Callable
        List<T> cutList;
        for (int i = 0; i < threadNum; i++) {
            if (i == threadNum - 1) {
                //最后一个线程处理的数据
                cutList = list.subList(i * handleSize, list.size());
            } else {
                cutList = list.subList(i * handleSize, (i + 1) * handleSize);
            }
            //截取的list
            final List<T> finalCutList = cutList;
            //循环Callable处理任务
            tasks.add(getCallable(objectMethod, finalCutList));
        }
        return tasks;
    }

    /**
     * 两个参数入参task异步任务获取
     *
     * @param threadNum    线程数
     * @param handleSize   每个线程处理list的大小
     * @param list         待处理list集合(入参一)
     * @param u            入参二
     * @param objectMethod 处理逻辑函数(BiFunction、BiConsumer)
     * @param <T>          待处理List集合元素类型
     * @param <U>          入参二的类型
     * @param <V>          返回值Callable中的元素类型
     * @return List<Callable < V>> 返回Callable集合
     */
    private static <T, U, V> List<Callable<V>> getTasksTwoParam(int threadNum, int handleSize, List<T> list, U u, Object objectMethod) {
        //收集Callable集合,不需要返回值
        List<Callable<V>> tasks = new ArrayList<>();
        //每个线程的处理任务Callable
        List<T> cutList;
        for (int i = 0; i < threadNum; i++) {
            if (i == threadNum - 1) {
                //最后一个线程处理的数据
                cutList = list.subList(i * handleSize, list.size());
            } else {
                cutList = list.subList(i * handleSize, (i + 1) * handleSize);
            }
            //截取的list
            final List<T> finalCutList = cutList;
            //循环Callable处理任务
            tasks.add(getCallableTwoParam(objectMethod, finalCutList, u));
        }
        return tasks;
    }

    /**
     * @param objectMethod 处理逻辑函数(Function、Consumer)
     * @param finalCutList 切割list
     * @param <T>          待处理集合list元素类型
     * @param <V>          返回值Callable中的元素类型
     * @return Callable<V> 返回一个Callable
     */
    @SuppressWarnings("unchecked")
    private static <T, V> Callable<V> getCallable(Object objectMethod, Collection<? extends T> finalCutList) {
        return new Callable<V>() {
            @Override
            public V call() {
                if (objectMethod instanceof Function) {
                    return ((Function<Collection<? extends T>, ? extends V>) objectMethod).apply(finalCutList);
                } else if (objectMethod instanceof Consumer) {
                    ((Consumer<Collection<? extends T>>) objectMethod).accept(finalCutList);
                    return null;
                }
                return null;
            }
        };
    }

    /**
     * @param objectMethod 处理逻辑函数(BiFunction、BiConsumer)
     * @param finalCutList 切割list(入参一)
     * @param u            入参二
     * @param <T>          切割list元素类型
     * @param <U>          入参二的类型
     * @param <V>          返回值Callable中的元素类型
     * @return Callable<V> 返回一个Callable
     */
    @SuppressWarnings("unchecked")
    private static <T, U, V> Callable<V> getCallableTwoParam(Object objectMethod, Collection<? extends T> finalCutList, U u) {
        return () -> {
            if (objectMethod instanceof BiConsumer) {
                ((BiConsumer<Collection<? extends T>, ? super U>) objectMethod).accept(finalCutList, u);
                return null;
            } else if (objectMethod instanceof BiFunction) {
                return ((BiFunction<Collection<? extends T>, ? super U, ? extends V>) objectMethod).apply(finalCutList, u);
            }
            return null;
        };
    }
}

 二、测试用例代码

package com.satoken;

import com.satoken.utils.ParallelHandleUtil;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * ParallelHandleUtil工具类测试用列
 *
 * @author hulei
 * @date 2024/3/6 10:11
 */

@SpringBootTest
class ParallelHandleUtilTest {
    @Test
    public void testParallelHandle() throws Exception {
        //初始数据准备
        List<Integer> list = new LinkedList<>();
        System.out.println("准备并行数据...");
        long start1 = System.currentTimeMillis();
        for (int i = 1; i <= 10; i++) {
            list.add(i);
        }
        long end1 = System.currentTimeMillis();
        System.out.println("并行数据准备完成,耗时:" + (end1 - start1) + "ms");

        System.out.println("开始并行处理...");
        long start2 = System.currentTimeMillis();
        //把每个元素加1的逻辑封装成函数
        Function<List<Integer>, List<Integer>> function = (List<Integer> handleList) -> handleList.stream().map(loop -> {
            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            return loop + 1;
        }).collect(Collectors.toList());
        //并行处理
        list = ParallelHandleUtil.functionHandle(2, 15, list, function);
        long end2 = System.currentTimeMillis();
        System.out.println("并行处理完成,耗时:" + (end2 - start2) + "ms");
        System.out.println("处理完成");
        System.out.println("并行处理结果:"+list);

        System.out.println("===================================================");

        System.out.println("准备串行数据...");
        List<Integer> list2 = new LinkedList<>();
        long start3 = System.currentTimeMillis();
        for (int i = 1; i <= 10; i++) {
            list2.add(i);
        }
        long end3 = System.currentTimeMillis();
        System.out.println("串行数据准备完成,耗时:" + (end3 - start3) + "ms");

        System.out.println("开始串行处理...");
        long start4 = System.currentTimeMillis();
        list2 = list2.stream().map(loop -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            loop = loop + 1;
            return loop;
        }).collect(Collectors.toList());
        long end4 = System.currentTimeMillis();
        System.out.println("串行处理完成,耗时:" + (end4 - start4) + "ms");
        System.out.println("串行处理完成");
        System.out.println("串行处理结果:"+list2);

    }

}

 测试结果对比如下:

可以看到明显的测试耗时比对 结果

主要原理是对大list切割,多任务处理提升处理效率。一般list较小或者处理逻辑简单,不推荐使用。

三、实战场景

以下是我的实际开发示例

场景,需要处理5000个条数的list,每一条有个url,都需要远程调用获取结果,原来用时1小时,优化后5分钟

两个函数的处理逻辑封装

核心是线程数的设定以及是否有io等耗时操作,cpu密集型操作如果很耗时也可使用,具体需要测试下时间是否有缩短

不会使用,评论区找我,关注点赞支持一波!

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值