强制Java流提前执行部分管道,以将阻塞任务提交到线程池

强制Java流提前执行部分管道,以将阻塞任务提交到线程池,java,java-stream,Java,Java Stream,我有一个要处理的对象列表,Java8流API看起来是最干净和可读的方式 但我需要在这些对象上执行的一些操作包括阻塞IO(比如读取数据库)——因此我想将这些操作提交给一个包含几十个线程的线程池 起初,我考虑按照以下思路做一些事情: myObjectList .stream() .filter(wrapPredicate(obj -> threadPoolExecutor.submit( () -> longQuery(obj)

我有一个要处理的对象列表,Java8流API看起来是最干净和可读的方式

但我需要在这些对象上执行的一些操作包括阻塞IO(比如读取数据库)——因此我想将这些操作提交给一个包含几十个线程的线程池

起初,我考虑按照以下思路做一些事情:

myObjectList
    .stream()
    .filter(wrapPredicate(obj -> threadPoolExecutor.submit(
            () -> longQuery(obj)          // returns boolean
    ).get())                              // wait for future & unwrap boolean
    .map(filtered -> threadPoolExecutor.submit(
            () -> anotherQuery(filtered)  // returns Optional
    ))
    .map(wrapFunction(Future::get))
    .filter(Optional::isPresent)
    .map(Optional::get)
    .collect(toList());
wrapPredicate
wrapFunction
仅用于检查异常重新触发

但是,很明显,调用
Future.get()
将阻塞流的线程,直到对给定对象的查询完成,并且流在此之前不会继续。因此一次只处理一个对象,线程池没有意义

我可以使用并行流,但是我需要希望默认的
ForkJoinPool
就足够了。或者只增加
“java.util.concurrent.ForkJoinPool.common.parallelism”
,但我不想为了这个流而更改整个应用程序的设置。我可以在自定义的
ForkJoinPool
中创建流,但是我看到了

因此,我最终得出了这样的结论,只是为了保证在等待未来完成之前,所有需要的任务都提交到线程池:

myObjectList
    .stream()
    .map(obj -> Pair.of(obj, threadPoolExecutor.submit(
                    () -> longQuery(obj)             // returns boolean
        ))
    )
    .collect(toList()).stream()                      // terminate stream to actually submit tasks to the pool
    .filter(wrapPredicate(p -> p.getRight().get()))  // wait & unwrap future after all tasks are submitted
    .map(Pair::getLeft)
    .map(filtered -> threadPoolExecutor.submit(
            () -> anotherQuery(filtered)             // returns Optional
    ))
    .collect(toList()).stream()                      // terminate stream to actually submit tasks to the pool
    .map(wrapFunction(Future::get))                  // wait & unwrap futures after all submitted
    .filter(Optional::isPresent)
    .map(Optional::get)
    .collect(toList());
有没有明显更好的方法来实现这一点


一种更优雅的方式,告诉流“现在对流中的每个对象执行流水线步骤,直到现在”,然后继续处理
.collect(toList()).stream()以外的内容
还有一种更好的方法来过滤
未来的影响
而不是将其打包到ApacheCommons
Pair
中,以便以后过滤
Pair::getRight
?或者是一种完全不同的解决方法?

您可以为Java 8并行流指定线程池。您不必更改应用程序设置。更多信息:。

我认为主要问题的答案是否定的。为了“执行”流,您需要一个终端操作。但可能还有改进的余地


您至少可以通过收集到地图而不是列表来消除该对:

stream.collect(toMap(Function.identity(),
                     obj -> threadPoolExecutor.submit(() -> longQuery(obj))))
      .entrySet()
      .stream()
      .filter(wrapPredicate(entry -> entry.getValue().get()))
      .map(Entry::getKey)
      ...
stream.collect(toMap(Function.identity(),
                     obj -> threadPoolExecutor.submit(() -> longQuery(obj))))
      .entrySet()
      .stream()
      .filter(wrapPredicate(entry -> entry.getValue().get()))
      .map(Entry::getKey)
      ...

请注意,仅当处理的对象中没有一个与另一个相等时,此操作才有效。它使代码略短,易于阅读,因为您不必自己创建配对/条目。

您可以通过使用

myObjectList.stream()
    .map(obj -> threadPoolExecutor.submit(
                    () -> longQuery(obj)? anotherQuery(obj).orElse(null): null))
    .collect(toList()).stream()
    .map(wrapFunction(Future::get))
    .filter(Objects::nonNull)
    .collect(toList());
一点是,如果您稍后向同一执行者提交另一个查询,那么并发性将不会有任何改善。因此,您可以在
longQuery
返回
true
后直接执行它。此时,
obj
仍在范围内,因此您可以将其用于另一个查询

通过提取
Optional
的结果,使用
null
表示失败,我们可以得到相同的缺席结果表示,无论是因为
longQuery
返回了
false
还是
另一个查询
返回了空的
Optional
。因此,在提取
未来的
结果之后,我们所要做的就是
.filter(Objects::nonNull)


在获得实际结果之前,您必须首先提交作业,收集
未来
s的逻辑不会改变。无论如何,这是没有办法的。其他方便的方法或框架所能提供的只是隐藏这些对象的实际临时存储。

您至少可以通过收集到地图而不是列表来摆脱这对对象:

stream.collect(toMap(Function.identity(),
                     obj -> threadPoolExecutor.submit(() -> longQuery(obj))))
      .entrySet()
      .stream()
      .filter(wrapPredicate(entry -> entry.getValue().get()))
      .map(Entry::getKey)
      ...
stream.collect(toMap(Function.identity(),
                     obj -> threadPoolExecutor.submit(() -> longQuery(obj))))
      .entrySet()
      .stream()
      .filter(wrapPredicate(entry -> entry.getValue().get()))
      .map(Entry::getKey)
      ...

也许只是我比那个流人更老派;但我并不觉得这个练习的结果如此令人愉快。有点难读;而获得所有微妙的细节会让“希望我永远不要接触和修改这段代码”。因为我太害怕去打破它。IMO在没有代码> >收集(TelistTo))时非常可读。我不认为如果在这里使用经典的
for
循环(但也许?我会试着问问同事他们的想法)。那么,对于循环和其他方法,可能会更容易阅读和维护。几乎,但不是真的。我提交
longQuery
以获取一个布尔值,在该布尔值上过滤原始对象。然后我用原始对象提交另一个查询,但只提交那些
longQuery
返回的
true
。其余的都是正确的–然后我阻塞等待
可选的
s,我再次将其解压缩到一个列表中。好的,明白了。我不会使用布尔结果从流中筛选元素。相反,如果结果为真,我将调用另一个查询,如果结果为假,我将什么也不做。我也永远不会调用
Future.get
进入流中。我只需将期货收集到一个列表中,然后使用番石榴或CompletableFuture。allOf将即
列表
合并到一个
CompletableFuture
,正如我在描述中所解释的那样,在自定义big
ForkJoinPool
中运行并行流并不能保证所需的并行化水平,因此它不是这个问题的解决方案。如果我能做到这一点,那就太完美了,但是并行流的批处理数可能仍然少于我指定的线程数。比较一下。它们是(或者至少应该是)独一无二的,所以收集地图是一个很好的建议,我没有考虑。谢谢。:)那看起来和我的一模一样。