提问者:小点点

为什么我应该在并行流中使用并发特性和收集?


为什么我应该在并行流中使用并发特性和收集:

List<Integer> list =
        Collections.synchronizedList(new ArrayList<>(Arrays.asList(1, 2, 4)));

Map<Integer, Integer> collect = list.stream().parallel()
        .collect(Collectors.toConcurrentMap(k -> k, v -> v, (c, c2) -> c + c2));

而不是:

Map<Integer, Integer> collect = list.stream().parallel()
        .collect(Collectors.toMap(k -> k, v -> v, (c, c2) -> c + c2));

换句话说,不使用这个特性有什么副作用,它对内部流操作有用吗?


共3个答案

匿名用户

这两个收集器以根本不同的方式运行。

首先,Stream框架会将工作负载拆分为可以并行处理的独立块(这就是为什么你不需要一个特殊的集合作为源,synizedList是不必要的)。

对于非并发收集器,每个块将通过使用收集器的供应商创建一个本地容器(这里是Map)并将其累积到本地容器(放入条目)来处理。这些部分结果必须合并,即一个映射已被放入另一个映射,以获得最终结果。

并发收集器支持并发累加,因此只会创建一个ContranstMap,所有线程同时累加到该映射中。所以完成后,不需要合并步骤,因为只有一个映射。

因此,这两个收集器都是线程安全的,但可能表现出完全不同的性能特征,具体取决于任务。如果Stream在收集结果之前的工作量很大,则差异可能可以忽略不计。如果像在您的示例中一样,在收集操作之前没有相关的工作,则结果在很大程度上取决于必须合并映射的频率,即发生相同的键,以及实际目标并发映射如何处理并发情况下的竞争。

如果大多数时候都有不同的键,那么非并发收集器的合并步骤可能与之前的放置步骤一样昂贵,从而破坏了并行处理的任何好处。但是如果您有很多重复的键,需要合并值,则对同一键的争用可能会降低并发收集器的性能。

所以没有简单的“哪个更好”的答案(好吧,如果有这样的答案,为什么还要添加另一个变体)。这取决于你的实际操作。你可以用预期的场景作为选择一个的起点,但应该用现实生活中的数据来衡量。由于两者是等价的,你可以随时改变你的选择。

匿名用户

首先,我给霍尔格的答案打了1分,这是一个很好的答案。我会试着简单一点,这样说:

并发-

非并发-

理解它(IMHO)最简单的方法是编写一个自定义收集器并使用它的每个方法:供应商、累加器、组合器。

这已经被覆盖在这里了

匿名用户

正因为如此:“内存一致性效应:与其他并发集合一样,线程中的操作在将对象作为键或值放入并发映射之前发生——在另一个线程中从并发映射中访问或删除该对象之后的操作之前。”