Java 8中的Streams问题

时间:2014-12-26 09:38:21

标签: java performance concurrency java-stream fork-join

根据这个article,Java中的Fork-Join架构存在一些严重的缺陷。根据我的理解,Java 8中的Streams在内部使用了Fork-Join框架。我们可以使用parallel()方法轻松地将流转换为并行。但是当我们向并行流提交长时间运行的任务时,它会阻塞池中的所有线程,请检查this。这种行为对于现实世界的应用程序是不可接受的。

我的问题是在高性能应用程序中使用这些结构之前我应该​​考虑的各种考虑因素(例如股权分析,股票市场代码等)

3 个答案:

答案 0 :(得分:2)

注意事项类似于多线程的其他用法。

  • 如果您知道帮助,则只使用多个线程。目标不是使用您拥有的每个核心,而是使用符合您要求的程序。
  • 不要忘记多线程会带来开销,而且这种开销可能会超过您获得的值。
  • 多线程可能会遇到大的异常值。当您测试性能时,您不仅要考虑吞吐量(应该更好),还要考虑延迟的分布(在极端情况下通常更糟)
  • 对于低延迟,尽可能少地在线程之间切换。如果你可以在一个线程中做所有事情,这可能是一个不错的选择。
  • 对于低延迟,您不想玩得很好,而是希望通过执行诸如将忙等待线程固定到隔离内核等操作来最小化抖动。您拥有的隔离内核越多,您运行线程池之类的垃圾核心就越少。

答案 1 :(得分:1)

流API使得并行性看似简单。如前所述,使用并行流加速应用程序需要在实际运行时环境中进行彻底分析和测试。我自己对并行流的经验表明以下内容(我确信这个列表还远未完成):

  • 对流的元素执行的操作的成本与并行化机器的成本决定了并行流的潜在好处。例如,使用紧密循环查找双精度数组中的最大值是非常快的,因为流开销是不值得的。一旦操作变得更加昂贵,平衡开始倾向于并行流API - 在理想条件下,例如,专用于单个算法的多核机器)。我鼓励你做实验。

  • 您需要有时间和精力来学习流API的内在函数。有意想不到的陷阱。例如,可以使用简单语句中的常规Iterator构造Spliterator。在引擎盖下,迭代器生成的元素首先被收集到一个数组中。根据迭代器产生的元素数量,这种方法变得非常或甚至太耗资源。

  • 虽然引用的文章似乎让我们完全受到Oracle的支配,但这并不完全正确。您可以编写自己的Spliterator,将输入拆分为特定于您的情况的块,而不是依赖于默认实现。或者,您可以编写自己的ThreadFactory(请参阅方法ForkJoinPool.makeCommonPool)。

  • 您需要注意不要产生死锁。如果在流的元素上执行的任务使用ForkJoinPool本身,则可能发生死锁。您需要学习如何使用ForkJoinPool.ManagedBlocker API及其使用(我发现它很容易理解)。从技术上讲,你告诉ForkJoinPool线程正在阻塞,这可能导致创建额外的线程以保持并行度不变。当然,额外线程的创建并不是免费的。

只是我的五美分......

答案 2 :(得分:1)

文章的重点(实际上有17篇)是指出F / J框架更像是一个研究项目,而不是一个通用的商业应用程序开发框架。

批评对象,而不是男人。当框架的主要问题是架构师是教授/科学家而非工程师/商业开发人员时,尝试这样做是最困难的。从文章中下载的PDF合并更多地涉及使用研究标准而不是工程标准的问题。

并行流工作正常,直到您尝试缩放它们。该框架使用拉动技术;请求进入提交队列,线程必须将请求拉出提交队列。任务返回到forking线程的双端队列,其他线程必须将任务拉出双端队列。这种技术不能很好地扩展。在推送技术中,每个任务分散到系统中的每个线程。这在大规模环境中效果更好。

除了来自Oracle的Paul Sandoz指出,缩放还有许多其他问题:例如,如果你有32个内核并且正在做Stream.of(s1,s2,s3,s4).flatMap(x - > x) .reduce(...)那么最多你只会使用4个核心。文章指出,使用可下载的软件,缩放不能很好地运行,并且必须使用顺序技术来避免堆栈溢出和OOME。

使用并行流。但要注意这些限制。