Java 限制并行流中并发计算(如使用fixedThreadPool)数量的最佳/最优雅的方法是什么

Java 限制并行流中并发计算(如使用fixedThreadPool)数量的最佳/最优雅的方法是什么,java,concurrency,java-8,Java,Concurrency,Java 8,假设lambda表达式消耗一定数量的资源(如内存),这是有限的,需要限制并发执行的数量(例如:如果lambda临时消耗100 MB(本地内存),并且我们希望将其限制为1GB,那么我们不允许超过10次并发计算) 限制并发执行次数的最佳方法是什么,例如 IntStream.range(0, numberOfJobs).parallel().foreach( i -> { /*...*/ }); ? 注意:一个明显的选择是执行类似嵌套的操作 double jobsPerThread =

假设lambda表达式消耗一定数量的资源(如内存),这是有限的,需要限制并发执行的数量(例如:如果lambda临时消耗100 MB(本地内存),并且我们希望将其限制为1GB,那么我们不允许超过10次并发计算)

限制并发执行次数的最佳方法是什么,例如

IntStream.range(0, numberOfJobs).parallel().foreach( i -> { /*...*/ });
?

注意:一个明显的选择是执行类似嵌套的操作

    double jobsPerThread = (double)numberOfJobs / numberOfThreads;
    IntStream.range(0, numberOfThreads).parallel().forEach( threadIndex ->
        IntStream.range((int)(threadIndex * jobsPerThread), (int)((threadIndex+1) * jobsPerThread)).sequential().forEach( i -> { /*...*/ }));
这是唯一的办法吗?它没有那么优雅。事实上,我想喝一杯

IntStream.range(0, numberOfJobs).parallel(numberOfThreads).foreach( i -> { /*...*/ });

根据您的用例,使用
CompletableFuture
实用程序方法可能更容易:

import static java.util.concurrent.CompletableFuture.runAsync;

ExecutorService executor = Executors.newFixedThreadPool(10); //max 10 threads
for (int i = 0; i < numberOfJobs; i++) {
    runAsync(() -> /* do something with i */, executor);
}

//or with a stream:
IntStream.range(0, numberOfJobs)
         .forEach(i -> runAsync(() -> /* do something with i */, executor));
导入静态java.util.concurrent.CompletableFuture.runAsync;
ExecutorService executor=Executors.newFixedThreadPool(10)//最多10个线程
for(int i=0;i/*使用i*/,executor做点什么);
}
//或使用流:
IntStream.range(0,numberOfJobs)
.forEach(i->runAsync(()->/*使用i*/,executor做点什么));

代码的主要区别在于,并行
forEach
仅在上一个作业结束后返回,而
runAsync
将在提交所有作业后立即返回。如果需要,有多种方法可以改变这种行为。

流使用a进行并行操作。默认情况下,它们使用的是
ForkJoinPool.commonPool()
,不允许在以后更改并发性。但是,您可以使用自己的
ForkJoinPool
实例。当您在自己的
ForkJoinPool
上下文中执行流代码时,此上下文池将用于流操作。下面的示例通过使用默认行为和使用固定并发度为
2
的自定义池执行相同的操作来说明这一点:

import java.util.HashSet;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.stream.IntStream;

public class InterfaceStaticMethod {
    public static void main(String[] arg) throws Exception {
      Runnable parallelCode=() -> {
        HashSet<String> allThreads=new HashSet<>();
        IntStream.range(0, 1_000_000).parallel().filter(i->{
          allThreads.add(Thread.currentThread().getName()); return false;}
        ).min();
        System.out.println("executed by "+allThreads);
      };
      System.out.println("default behavior: ");
      parallelCode.run();
      System.out.println("specialized pool:");
      ForkJoinPool pool=new ForkJoinPool(2);
      pool.submit(parallelCode).get();
    }
}
import java.util.HashSet;
导入java.util.concurrent.ExecutionException;
导入java.util.concurrent.ForkJoinPool;
导入java.util.stream.IntStream;
公共类接口方法{
公共静态void main(字符串[]arg)引发异常{
可运行并行代码=()->{
HashSet allThreads=新HashSet();
IntStream.range(0,1_000_000).parallel().filter(i->{
allThreads.add(Thread.currentThread().getName());返回false;}
).min();
System.out.println(“由”+所有线程执行);
};
System.out.println(“默认行为:”);
parallelCode.run();
System.out.println(“专用池:”);
ForkJoinPool池=新的ForkJoinPool池(2);
pool.submit(parallelCode.get();
}
}

为什么不能使用共享的固定线程池?并行流会使事情变得复杂吗?我确实在Java6中使用了一个共享的固定线程池,但Java8代码要简洁得多。对于线程池,我必须定义一个未来的ArrayList,将worker提交给执行者,从未来收集结果。虽然这是主要的动机,但我的印象是流更高效(大约10%)。@AlexeiKaigorodov感谢您提供了创建具有给定目标并行级别的ForkJoinPool或更改当前目标并行级别的提示。这也是一个很好的解决方案。这也是一个很好的解决方案,但要使其完整(即与其他解决方案进行公平比较),必须添加连接所有线程所需的代码行。实际上,正是这些额外的代码使得使用ExecutorService看起来更冗长。是的,确实如此-根据您的用例,您可以使用
thenXXX
方法之一,或者您可以关闭executor(它将连接所有线程)。如果这不起作用,那么您可能必须检索未来,它将变得非常接近不使用流的代码。有趣的是,我本以为
parallel()
将始终使用自己的池(ForkJoin公共池),但它没有。由
parallel()
使用的池未指定。当前行为如图所示,但将来可能会发生变化。