Parallel processing 流处理线程池耗尽

Parallel processing 流处理线程池耗尽,parallel-processing,java-8,threadpool,java-stream,Parallel Processing,Java 8,Threadpool,Java Stream,我发现JVM只使用一个线程池来并行处理流,这是一个艰难的过程。我们在一个大的流上有一个I/O阻塞函数,这导致了与不相关的并行流一起使用的不相关或其他快速函数的活跃性问题 流上没有允许使用备用线程池的方法 有没有一种简单的方法可以避免这个问题,也许可以指定使用哪个线程池?这可能与 这一问题将在本文中进一步讨论 我编写了一个名为的小型库,它可以向自定义FJP提交任务。这样你就可以写作了 ForkJoinPool forkJoinPool = new ForkJoinPool(2); int[] pr

我发现JVM只使用一个线程池来并行处理流,这是一个艰难的过程。我们在一个大的流上有一个I/O阻塞函数,这导致了与不相关的并行流一起使用的不相关或其他快速函数的活跃性问题

流上没有允许使用备用线程池的方法


有没有一种简单的方法可以避免这个问题,也许可以指定使用哪个线程池?

这可能与

这一问题将在本文中进一步讨论


我编写了一个名为的小型库,它可以向自定义FJP提交任务。这样你就可以写作了

ForkJoinPool forkJoinPool = new ForkJoinPool(2);
int[] primes = IntStreamEx.range(1, 1_000_000)
    .parallel(forkJoinPool)
    .filter(PrimesPrint::isPrime).toArray();

它只需记住您的池并启动池内的终端操作即可加入结果。只是前面提到的解决方案的一个语法糖。

您可以将阻塞操作包装为一个,大致如下:

static <T> Supplier<T> blocking(Supplier<T> supplier) {
    return new Supplier<T>() {
        volatile T result;

        @Override
        public T get() {
            try {
                ForkJoinPool.managedBlock(new ManagedBlocker() {
                    @Override
                    public boolean block() {
                        result = supplier.get();
                        return true;
                    }

                    @Override
                    public boolean isReleasable() {
                        return result != null;
                    }
                });
            }
            catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            return result;
        }
    };
}
更多信息可在此博客帖子中找到:

通过为所有Java 8
functional接口
类型提供包装器,如上面所述,因此您可以编写:

Stream.generate(Blocking.supplier(() -> ...))
      .parallel()
      ...
      .collect(...);
或者,例如,当过滤器堵塞时:

Stream....
      .parallel()
      .filter(Blocking.predicate(t -> blockingTest(t)))
      .collect(...);

(免责声明,我为jOOλ背后的公司工作)。

可能的重复有一个技巧——在上面的链接问题中建议——但你应该记住,流主要是一种并行计算机制,而不是IO,因此你是在边缘操作。我们正在研究如何将这些用例更多地带入主流。@ BrangoEtz,当我听到你的耳朵时,请考虑API的扩展,也许是并行()/<代码>方法的参数,这允许一个单独的线程池用于并行流处理——一些计算也很繁重,可能会导致活力问题。虽然我认为“非常长”的时间(如IO)超出了设计范围,但使用流来驱动这种使用是很自然的,而且应该有一种简单的方法来减轻这种使用的影响。对我来说,这打破了乔希“最不惊讶”的规则。圣诞快乐:)@Bohemian如果你不是第一个提出这个建议,你可能不会感到惊讶。然而,我们有充分的理由不做出这个“明显的”选择,我仍然相信我们做出了正确的选择。IIRC你声称jOOλ不关心并行处理:-)实际上你的解决方案(和我的一样)还有另一个问题。流API创建的子任务数通常是处理器数的4倍(对于无限流可能更多)。如果我们使用100个线程创建自定义池,或者使用您的方法将任务标记为阻塞,那么这个数字不会改变。为了阻止IO,实际上最好使用其他东西(例如CompletableFutures)。@TagirValeev:jOOλ的目的是“修复”整个JDK(例如,还提供抛出已检查异常的功能接口)。jOOλ的
Seq
的目的是“固定”
,以便顺序(和有序)使用
CompletableFuture
还将任务发送到
ForkJoinPool
,因此您可能会耗尽相同的线程。我同意这种用法可能不是人们应该做的。但话说回来,什么是阻塞(在这种情况下)?是I/O吗?它是一个循环直到
1\u 000\u 000\u 000
?这是一个愚蠢的指数算法吗?OP明确地说这是I/O。使用CF可以创建任意多的任务,但使用并行流您无法控制它。在4核机器上,通常会创建多达16个任务,如果其中15个任务在I/O操作中被阻塞,那么即使使用您的技巧,也只有一个任务会继续工作。流API将不再分割输入,也不会为您创建更多任务。
Stream.generate(Blocking.supplier(() -> ...))
      .parallel()
      ...
      .collect(...);
Stream....
      .parallel()
      .filter(Blocking.predicate(t -> blockingTest(t)))
      .collect(...);