我想全局替换Java并行流默认使用的公共线程池,例如
IntStream.range(0,100).parallel().forEach(i -> {
doWork();
});
我知道可以通过向专用线程池提交此类指令来使用专用的ForkJoinPool(请参阅Custom thread pool in Java 8 parallel stream)。这里的问题是
Executors.newFixedThreadPool(10)
?备注:我之所以要更换F / J池,是因为它似乎有一个错误,使其无法用于嵌套的并行循环。
嵌套并行循环的性能很差,可能会导致死锁,请参阅http://christian-fries.de/blog/files/2014-nested-java-8-parallel-foreach.html
例如:以下代码导致死锁:
// Outer loop
IntStream.range(0,24).parallel().forEach(i -> {
// (omitted:) do some heavy work here (consuming majority of time)
// Need to synchronize for a small "subtask" (e.g. updating a result)
synchronized(this) {
// Inner loop (does s.th. completely free of side-effects, i.e. expected to work)
IntStream.range(0,100).parallel().forEach(j -> {
// do work here
});
}
});
(即使没有任何额外的代码,“在这里工作”,假设并行性设置为< 12)。
我的问题是如何更换FJP。如果您想讨论嵌套并行循环,可以检查Nested Java 8 parallel forEach loop perform poor. Is this behavior expected?。
答案 0 :(得分:5)
我认为这不是流API的使用方式。似乎你(错误地)使用它来执行并行任务执行(专注于任务,而不是数据),而不是进行并行流处理(专注于流中的数据)。您的代码以某种方式违反了流的一些主要原则。 (我以某种方式写作'因为它并不是真的被禁止但是气馁):避免states and side effects。
除此之外(或者可能是因为副作用),你在外循环中使用了大量同步,这是其他一切,但无害!
虽然文档中没有提到,但并行流在内部使用common ForkJoinPool
。无论这是否缺乏文件,我们都必须接受这一事实。 JavaDoc of ForkJoinTask
州:
可以定义和使用可能阻塞的ForkJoinTasks,但这样做需要进一步考虑:(1)如果任何其他任务应该依赖于阻止外部同步或I / O的任务,则完成很少。从未加入的事件类型异步任务(例如,那些子类化CountedCompleter)通常属于此类别。 (2)为了尽量减少资源影响,任务应该很小;理想情况下只执行(可能)阻止操作。 (3)除非使用ForkJoinPool.ManagedBlocker API,或者已知可能被阻塞的任务的数量小于池的ForkJoinPool.getParallelism级别,否则池不能保证有足够的线程可用于确保进度或表现不错。
同样,您似乎正在使用流来替代简单的for循环和执行服务。
n
个任务,请使用ExecutionService
ForkJoinPool
(使用ForkJoinTasks
)。 (它确保了一定数量的线程,没有死锁的危险,因为等待其他任务完成的任务太多,因为等待的任务不会阻塞它们的执行线程。)不要混淆ExecutionService
和ForkJoinPool
。他们(通常)不是彼此的替代品!
答案 1 :(得分:1)
虽然您的原始问题已经得到了很好的回答 isnot2bad,对于您而言,所描述的错误(您希望交换FJP实现的原因)似乎可以通过java 1.8.0.40修复。见Nested Java 8 parallel forEach loop perform poor. Is this behavior expected?