Java 并发执行:未来vs并行流

Java 并发执行:未来vs并行流,java,performance,parallel-processing,java-stream,future,Java,Performance,Parallel Processing,Java Stream,Future,我编写了一个callable,它轮询远程客户端以获取信息,并以列表形式返回该信息。我使用threadpoolexecutor、for循环和Future对多个远程客户端并行执行任务。然后,我将所有未来的列表与addAll()组合,并使用巨大的组合列表 我的问题是,在这里使用parallelstream()会比使用future和for循环更有效吗?编写代码当然更容易!如果我走那条路,我还会需要threadpoolexecutor吗 谢谢大家! for(SiteInfo site :

我编写了一个callable,它轮询远程客户端以获取信息,并以列表形式返回该信息。我使用threadpoolexecutor、for循环和Future对多个远程客户端并行执行任务。然后,我将所有未来的列表与addAll()组合,并使用巨大的组合列表

我的问题是,在这里使用parallelstream()会比使用future和for循环更有效吗?编写代码当然更容易!如果我走那条路,我还会需要threadpoolexecutor吗

谢谢大家!

        for(SiteInfo site : active_sites) {
            TAG_SCANNER scanr = new TAG_SCANNER(site, loggr);
            Future<List<TagInfo>> result = threadmaker.submit(scanr);

            //SOUND THE ALARMS
            try {
                alarm_tags.addAll(result.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        }

一般来说,
parallelstream
是由非常聪明的程序员编写的,可以非常有效地进行并行处理

因此,与所有其他java线程(如并发包)一样,除非您是这方面的专家,否则如果您自己编写,您可能会:

  • 慢跑
  • 引入bug
  • 具有更复杂/更难理解的/etc代码

换句话说:是的,使用parallelstream

这里有一些误解。首先,如果调用
Future,则使用异步任务不会提高资源利用率。在提交任务后立即获取
,在提交下一个任务之前立即等待任务完成

其次,Netbeans进行的代码转换产生了一个大致相当的代码,仍然将任务提交给
执行者
,因此这不是“未来vs并行流”的问题,因为您只使用并行流执行提交(并等待),并且仍然使用执行者。由于您的第一个错误,并行执行可能会提高吞吐量,但除此之外,将两个错误合并在一起以消除它们本身并不是一个好主意,这仍然是一个糟糕的解决方案:

Stream API的标准实现针对CPU绑定的任务进行了优化,创建了大量与CPU内核数量匹配的线程,并且在等待操作中阻塞这些线程时不会生成新线程。因此,使用并行流来执行I/O操作,或者通常是可能等待的操作,并不是一个好的选择。您无法控制实现所使用的线程

更好的选择是使用
ExecutorService
,您可以根据远程客户端的预期I/O带宽对其进行配置。但您应该修复提交后立即等待、先提交所有任务,然后等待所有任务完成的错误。请注意,您可以为此使用流API,这不是为了更好的并行性,而是为了潜在地提高可读性:

// first, submit all tasks, assuming "threadmaker" is an ExecutorService
List<Future<List<TagInfo>>> futures=threadmaker.invokeAll(
    active_sites.stream()
        .map(site -> new TAG_SCANNER(site, loggr))
        .collect(Collectors.toList())
);
// now fetch all results
for(Future<List<TagInfo>> result: futures) {
    //SOUND THE ALARMS
    try {
        alarm_tags.addAll(result.get());
    } catch (InterruptedException | ExecutionException e) {
        // not a recommended way of handling
        // but I keep your code here for simplicity
        e.printStackTrace();
    }
}
//首先,提交所有任务,假设“threadmaker”是一个ExecutorService
List futures=threadmaker.invokeAll(
活动站点。流()
.map(站点->新标签扫描仪(站点,日志))
.collect(收集器.toList())
);
//现在获取所有结果
for(未来结果:未来){
//拉响警报
试一试{
alarm_tags.addAll(result.get());
}捕获(中断异常|执行异常e){
//不是推荐的处理方式
//但为了简单起见,我将您的代码保留在这里
e、 printStackTrace();
}
}

请注意,此处使用的流API是顺序的,仅用于将
SiteInfo
列表转换为
Callable
列表,但您可以使用循环执行相同操作。

@sinu-code-posted。使用该实现肯定会更快,因为您当前的实现是顺序的:提交任务后,您不会提交其他任务并等待所有任务完成,而是等待当前任务完成,然后再提交下一个任务。所以在一个线程中完成所有任务会更快。我想我现在明白了。我原以为未来会使事情并行运行,但我想for循环是保持事情顺序的原因?是的!除非重写我所拥有的使其并行运行将产生更好的结果?否。在提交任务后立即调用get()是使其连续的原因。在并行流中引入此类错误的可能性要小得多,但您无法确定允许多少并发线程执行您的任务。并行流已经将其操作并行化了。使用并行流时,不需要将任务提交给执行者。只需像对连续流那样编程即可。酷!这使事情更具可读性,NetBeans几乎为我完成了所有的转换工作。谢谢@弗兰克:别这么匆忙。你真的明白了吗?@Holger我想是的,是的。对get()的调用阻止了for循环的迭代,因为我正在等待在重新启动循环之前获得结果?我假设parallelstream不会有这个问题,因为parallelstream之后的所有内容都可以并发运行。有什么我遗漏的吗?@TheFunk:如果你打算使用流API进行并行处理,你应该始终如一地这样做,完全取消对执行器的使用,因为只有这样,你才能假设它将防止你在执行器上产生的bug。但是对于这个特定的任务,使用I/O,流API并不是最好的选择。谢谢!我认为我对未来的工作方式和流API的工作方式的理解现在都有点好了!你的回答澄清了我一直有点困难的一些事情。
// first, submit all tasks, assuming "threadmaker" is an ExecutorService
List<Future<List<TagInfo>>> futures=threadmaker.invokeAll(
    active_sites.stream()
        .map(site -> new TAG_SCANNER(site, loggr))
        .collect(Collectors.toList())
);
// now fetch all results
for(Future<List<TagInfo>> result: futures) {
    //SOUND THE ALARMS
    try {
        alarm_tags.addAll(result.get());
    } catch (InterruptedException | ExecutionException e) {
        // not a recommended way of handling
        // but I keep your code here for simplicity
        e.printStackTrace();
    }
}