【问题标题】:What's the best way to parallelize a REST call?并行化 REST 调用的最佳方式是什么?
【发布时间】:2018-09-20 07:10:38
【问题描述】:

我正在编写一些处理多个 REST 调用的 java 代码

call1()
call2()
call3()
...

我想并行化这些调用,但同步执行我的主要代码。 我用lamba和并行流做了一个POC:

List<Runnable> list = new ArrayList();
list.add(() -> {call1()});
list.add(() -> {call2()});
list.add(() -> {call3()});
list.add(...);

list.parallelStream()
            .forEach(Runnable::run);

您还有其他解决方案吗? 我还检查了是否使用来自 Jersey 客户端的异步调用,但这需要更多的代码更改。

【问题讨论】:

  • “最好”,在你正在寻找的东西中,是否意味着“使用最少的代码”?有人可能会争辩说,“在相同的时间内完成的同时阻塞最少的线程”是一种获得更好结果的明智方式。那么什么对你来说“更好”呢?
  • 最佳意味着最佳性能,具有正确的可读性和更少的核心代码更改

标签: java multithreading rest java-stream


【解决方案1】:

您正在寻找的只是异步运行您的调用。您可以使用CompletableFutures 提交任务,然后等待他们完成:

list.stream() //you don't really need a parallel stream
    .map(CompletableFuture::runAsync)
    .collect(Collectors.toList()) //make sure all tasks are submitted
    .stream()
    .forEach(CompletableFuture::join);

这将提交所有任务(异步运行),然后等待每个任务完成运行。发生这种情况时,该方法将返回。

您可能需要控制异步任务的线程池。这是一个使用 10 线程池的示例:

ExecutorService es = Executors.newFixedThreadPool(10);
list.stream()
    .map(r -> CompletableFuture.runAsync(r, es))
     ...

【讨论】:

  • 您能否详细说明为什么您推荐 CompletableFuture 而不是并行流?
  • 我的印象是(没有足够的阅读来确认),并行流在默认的 ForkJoin 池上工作,CompletableFuture runAsync 任务也一样(没有其他参数)。在这种情况下,性能可能与这两种方法“不相上下”,除非一个方法指定了显式线程池。对吧?
  • @BertrandCedric :可以“假设”您的外部 REST 调用可能不受 CPU 限制:给定线程实际上需要很少的 CPU 周期,并且处于空闲状态(等待响应)。例如:imagine 一个调用需要 100 毫秒。 90ms 将用于您的线程空闲,10ms 将用于实际计算(JSON 序列化等)。因此,对于单个 CPU 秒,您可以有 100 个线程处于活动状态(100 个线程 * 10 毫秒 = 1 秒),而实际上并没有浪费时间。如果你有 8 个线程,你的 CPU 将工作 8*10ms = 80ms 并等待 920 毫秒。这是虚构的,但你明白了。
  • @BertrandCedric 流只是提交任务。如果任务是同步执行的,那么没有区别。但是对于异步任务,任务提交(在此示例中使用 fork-join 池)可能比任务执行(例如,单线程池)使用更多的并行性。在我看来,服务调用(或其他典型任务)通常比将它们提交给执行器服务所需的时间更长(因此,执行的并行性比提交的并行性更重要)。
  • 注意ExecutorService.invokeAll的存在。它期望Callable 而不是Runnable,但通常,这样的操作无论如何都会有结果。
猜你喜欢
  • 1970-01-01
  • 2019-08-03
  • 2015-09-10
  • 2017-07-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-08-10
相关资源
最近更新 更多