为什么我要在并行流中使用并发特性和collect?
为什么我要在并行流中使用并发特性和collect:
List list = Collections.synchronizedList(new ArrayList(Arrays.asList(1, 2, 4))); Map collect = list.stream().parallel() .collect(Collectors.toConcurrentMap(k -> k, v -> v, (c, c2) -> c + c2));
并不是:
Map collect = list.stream().parallel() .collect(Collectors.toMap(k -> k, v -> v, (c, c2) -> c + c2));
换句话说,不使用此特性会产生什么副作用,它对内部流操作有用吗?
这两个collections家以完全不同的方式运作。
首先,Stream框架将工作负载拆分为可以并行处理的独立块(这就是为什么您不需要特殊集合作为源,不需要synchronizedList
)。
对于非并发收集器,将使用收集器的供应商创建本地容器(此处为Map
)并累积到本地容器(放置条目)来处理每个块。 必须合并这些部分结果,即将一个地图放入另一个地图,以获得最终结果。
并发收集器支持并发累积,因此只会创建一个ConcurrentMap
并且所有线程同时累积到该映射中。 因此,完成后,不需要合并步骤,因为只有一个地图。
因此,两个收集器都是线程安全的,但可能会表现出完全不同的性能特征,具体取决于任务。 如果Stream在收集结果之前的工作量很大,那么差异可能会微不足道。 如果像在您的示例中那样,在收集操作之前没有相关工作,则结果在很大程度上取决于映射必须合并的频率,即发生相同的密钥,以及实际目标ConcurrentMap
如何处理并发情况下的争用。
如果您主要使用不同的密钥,则非并发收集器的合并步骤可能与之前的放置一样昂贵,从而破坏了并行处理的任何好处。 但是,如果您有许多重复的密钥,需要合并这些值,则对同一密钥的争用可能会降低并发收集器的性能。
所以没有简单的“哪个更好”的答案(好吧,如果有这样的答案,为什么还要加入另一个变体)。 这取决于您的实际操作。 您可以使用预期方案作为选择方案的起点,但应使用实际数据进行衡量。 由于两者都是等价的,您可以随时更改您的选择。
首先,我给了霍尔格的答案+1,这是一个很好的答案。 我会试着简单地说一下,说:
CONCURRENT – >多个线程以无特定顺序在同一个容器中抛出数据(ConcurrentHashMap)
非并发 – >多个线程组合其中间结果。
理解它(IMHO)的最简单方法是编写一个自定义收集器并使用它的每个方法:供应商,累加器,组合器。
这已经在这里涵盖了
正因为如此:“内存一致性影响:与其他并发集合一样,在将对象作为键或值放入ConcurrentMap之前的线程中的操作发生在从另一个中的ConcurrentMap访问或删除该对象之后的操作之前线。”