说明:本工具类不适用于所有场景,如果你的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密集型操作如果很耗时也可使用,具体需要测试下时间是否有缩短
不会使用,评论区找我,关注点赞支持一波!