弱鸡求教一个关于 Java 多层 for 循环效率问题

2023-03-10 14:56:34 +08:00
 Gct012

我是一个菜鸡开发,目前遇到一个需求想请教下各位大佬。假设需要改造以下 For 循环

for(int i = 0; i < list.size() ; i ++){

  List resultList = HttpRequest.post(url).body(list.get(i));
  
  for(int j = 0 ; j < resultList.size() ; j++){
    var resultA = functionA(resultList.get(j));
    var resultB = functionB(resultA);
    var resultC = functionC(resultB);
  }
}

其中 list 数据来源 API 接口,数据量在 100 到 1000 不等。functionA 、B 、C 都有业务逻辑( Http 请求,数据库查询等,都是需要串行执行的)。目前单线程运行比较慢,想问下有什么比较好的办法可以提高处理效率?

我打算使用多线程并行处理 list 的数据,但是里面那层 for 循环数据量也比较大(多的可能有 1 万条),里面那层不知道有没有办法也可以加快效率的?或者针对这类场景是否有比较通用的解决办法?

2976 次点击
所在节点    程序员
31 条回复
Aluneth
2023-03-10 16:18:09 +08:00
能不能 f 将 uncABC 视为一个函数,去掉外部循环,将需要跟中间件交流的地方都改成批量执行。最终还是要看这个循环耗时的点具体在什么地方。
RyanLeeCUP
2023-03-10 16:28:59 +08:00
@Gct012 不是库 类似伪代码,担心并发量就分批,然后把串行操作抽象成一个节点,节点任务并行化,最后会成管道式的流程,最后管道本身也可以被并行化 按这个思路去设计
ldyisbest
2023-03-10 16:30:31 +08:00
List resultList = HttpRequest.post(url).body(list.get(i)); 提到最外层, 用批量了接口,思想是减少 http 请求次数,functionA,B,C 里面也这样做
levintrueno
2023-03-10 17:51:44 +08:00
public class Code {

// 执行外层任务的线程池
static ExecutorService outerExecutor = Executors.newFixedThreadPool(8);

// 执行内层任务的线程池
static ExecutorService innerExecutor = Executors.newFixedThreadPool(16);

// 任务总数
static AtomicInteger taskCount = new AtomicInteger();

static String url = "url";

static Random random = ThreadLocalRandom.current();

public static void optimization() {

StopWatch stopWatch = new StopWatch();
stopWatch.start();

// 模拟任务
final int maxTask = random.nextInt(1000);

System.out.println("外层总任务数:" + maxTask);

List<String> list = IntStream.rangeClosed(1, maxTask).mapToObj(String::valueOf).collect(Collectors.toList());

// 50 个任务一组
final List<List<String>> partition = Lists.partition(list, 50);

System.out.println("拆分任务数量:" + partition.size());

partition.parallelStream()
.map(task -> CompletableFuture.runAsync(new OuterTask(task), outerExecutor))
.forEach(CompletableFuture::join);

System.out.println("taskCount = " + taskCount);
stopWatch.stop();
System.out.println("耗时:" + stopWatch.getTotalTimeSeconds());

innerExecutor.shutdown();
outerExecutor.shutdown();

}

private static class OuterTask implements Runnable {

private final List<String> tasks;

public OuterTask(List<String> tasks) {
this.tasks = tasks;
}

@Override
public void run() {
tasks.parallelStream()
.map(task -> CompletableFuture.runAsync(new InnerTask(task), innerExecutor))
.forEach(CompletableFuture::join);
}
}

private static class InnerTask implements Runnable {

private final String body;

public InnerTask(String body) {
this.body = body;
}

@Override
public void run() {
final List<String> responseResult = HttpRequest.post(url).body(body);

for (String aParam : responseResult) {
final String bParam = functionA(aParam);
final String cParam = functionB(bParam);
final String result = functionC(cParam);

// handle result...

taskCount.incrementAndGet();
}
}
}
}

考虑不周,仅作参考。。。
Dahunvwu
2023-03-10 17:58:03 +08:00
disruptor 框架了解一下,或者使用 1.8 的 CompletionService
disruptor.handleEventsWithWorkerPool(poolA)
.thenHandleEventsWithWorkerPool(poolB)
.thenHandleEventsWithWorkerPool(poolC)
.thenHandleEventsWithWorkerPool(poolD)
ymz
2023-03-10 18:04:42 +08:00
线程池,以及 resultList 需要和数据库打交道的一起提出来,切分后一条 sql 处理若干个,CPU 计算很快的,主要还是 IO
ration
2023-03-10 18:30:02 +08:00
看看瓶颈在哪里,http 请求还是数据库,添加日志看看时间多少。接着有些能合并请求的合并,多线程作为最后的手段。曾经试过下载文件的,实际上太多线程没用,网络限制在那里,提高带宽就好了。
janus77
2023-03-10 18:37:44 +08:00
你拿到 A 只是为了传入 B ?拿到 B 只是为了传入 C ?那可以把 funcABC 压缩合并简化一下
night98
2023-03-10 22:28:57 +08:00
第二行可以看看接口提供方能否提供批量接口,这样速度会快一点,然后下面的 abc 方法只能通过增加线程数的方式提升效率,或者上异步。
L0L
2023-03-10 22:47:02 +08:00
@luckyrayyy 20 楼流操作大师,并发,还不会冲突。
ychost
2023-03-10 22:55:25 +08:00
Java19 的 Loom 开一开试一试,不行的话实时 reactor

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/922893

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX