当前位置: 首页 > 知识库问答 >
问题:

并行流是否能在不同的操作下工作得很好?

宗乐池
2023-03-14

我在读关于无国籍状态的文章时,在Doc中看到了这句话:

如果流操作的行为参数是有状态的,则流管道结果可能是不确定的或不正确的。有状态的lambda(或实现适当功能接口的其他对象)的结果取决于流管道执行过程中可能改变的任何状态。

现在,如果我有一个字符串列表(strlist),然后尝试使用并行流以以下方式从其中删除重复的字符串:

List<String> resultOne = strList.parallelStream().distinct().collect(Collectors.toList());
List<String> result2 = strList.parallelStream().map(String::toLowerCase)
                       .distinct().collect(Collectors.toList());

这段代码是否有问题,因为并行流会分割输入,并且在一个块中的distinct不一定意味着在整个输入中的distinct?

distince是一个有状态操作,在有状态中间操作的情况下,并行流可能需要多次传递或大量缓冲开销。此外,如果元素的排序不相关,则distinct可以更有效地实现。根据单据:

对于有序流,不同元素的选择是稳定的(对于重复的元素,在遇到顺序中首先出现的元素被保留。)对于无序流,不作稳定性保证。

但是,在有序流并行运行的情况下,distinct可能是不稳定的--意味着在重复的情况下,它将保留一个任意的元素,而不一定像distinct所期望的那样保留第一个元素。

从链接:

在内部,distinct()操作保留了一个包含前面已经看到的元素的集合,但它隐藏在操作内部,我们无法从应用程序代码中获取它。

因此,在并行流的情况下,它可能会消耗整个流或使用CHM(类似于concurrenthashMap.newkeyset())。对于有序的,很可能使用linkedhashset或类似的结构。

共有1个答案

戚鸿福
2023-03-14

粗略指出doc的相关部分(重点,我的):

中间操作进一步分为无状态和有状态操作。无状态操作(如filter和map)在处理新元素时不会保留以前看到的元素的状态--每个元素都可以独立于对其他元素的操作进行处理。当处理新元素时,诸如distinct和sorted的有状态操作可以合并来自先前看到的元素的状态

有状态操作可能需要在生成结果之前处理整个输入。例如,在看到流的所有元素之前,无法从对流的排序中产生任何结果。因此,在并行计算下,一些包含有状态中间操作的流水线可能需要对数据进行多次传递,或者可能需要缓冲重要的数据。包含排他性无状态中间操作的管道可以在一次传递中处理,不管是顺序操作还是并行操作,只需最少的数据缓冲

...

对于并行流,放松排序约束有时可以实现更高效的执行。如果元素的排序不相关,则可以更有效地实现某些聚合操作,例如筛选重复项(distinct())或分组约简(Collectors.GroupingBy())。类似地,在本质上绑定到遇到顺序的操作(如limit())可能需要缓冲以确保正确的顺序,从而破坏了并行性的好处。在流有遇到顺序但用户并不特别关心遇到顺序的情况下,使用unordered()显式地解除流的顺序可能会提高某些状态操作或终端操作的并行性能。然而,大多数流管道,例如上面的“块权重之和”示例,即使在排序约束下仍然高效地并行化。

最后,

    null
List<String> result2 = strList.parallelStream()
                              .unordered()
                              .map(String::toLowerCase)
                              .distinct()
                              .collect(Collectors.toList());

可惜Java中没有(可用的内置)并发哈希集(除非他们通过concurrenthashmap变得聪明),所以我只能给您留下一个不幸的可能性,即distinct是使用常规Java集以阻塞方式实现的。在这种情况下,我看不出做一个并行的distinct有什么好处。

编辑:我说得太早了。使用带有distinct的并行流可能会有一些好处。看起来distince的实现比我最初想象的更巧妙。参见@尤金的回答。

 类似资料:
  • 我正在尝试使用并行流连接字符串。 我在下面的代码中也发现了同样的问题。 在这里,我还使用了一个同步集合,所有的方法都是线程安全的。 我在Java文档中看到了这个 我是不是漏掉了什么?使用线程安全的数据结构还不够吗?

  • 我最近将我的项目与github操作连接起来,用于持续集成。我创建了两个独立的作业:第一个检查拉取请求中的代码是否被我们的linter接受,第二个检查代码是否通过测试套件。我喜欢有两个这样的工作在Github网页上显示为两个单独的选中标记,用于拉取请求: 我现在遇到的问题是工作流YAML文件中有一些重复的代码:前3个步骤,安装Lua和Luarock。不仅维护起来很烦人,而且运行两次相同的操作也会浪费

  • 我正在创建一个用户界面,允许用户通过拖放界面创建数据库表(及其字段和关系)。 这是我创建的jsFiddle,尽管它看起来有很多事情要做,但实际上它只是演示问题所需的最低限度。以下是我的要求,jsPlumb很好地单独处理了这些要求,但是当我试图把它们放在一起时,我会遇到问题。特别是,它将#2和#3结合在一起是一个问题。 表格可以在画布上拖动(使用jsPlumb.draggable()) 表中的字段可

  • 我在GitHub中有一个包含多个服务的monorepo 我想在两个条件下同时构建它们(使用GitHub操作): 标记-使用标记名称构建图像() 分支main-使用带有缓存的标签构建图像。 我找不到如何创建通用工作流和修改参数(如函数)的方法。 我能想到的唯一解决方案是创建一个复合操作,如下所示: 但现在我遇到了一个问题,需要在两个工作流中指定服务的所有作业,例如: 主要分支机构工作流: 标记分支工

  • 这是html文件 当我在浏览器中运行这个时,请求不会被发送(我在后端的跟踪球中看不到任何东西),url工作正常,如果在浏览器和邮递员中使用,这是邮递员请求和响应的截图

  • 我有以下GitHub操作的文件(删除了一些代码,以便更容易理解这个问题): 我经历了以下步骤: 在分支上进行一些提交,并推动这些更改 我期望GitHub Actions在第2项之后运行测试和部署阶段作业。但是相反,它只是再次运行,而没有运行。 如上所述,即使在推送到之后,它仍然在分支上运行,而不是在分支上运行。我有点假设这可能是由于一些奇怪的行为与快进合并。但是GitHub显然意识到我推动了,因为