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

Java 为什么我应该在并行流中使用并发特性和collect?,java,multithreading,concurrency,java-8,java-stream,Java,Multithreading,Concurrency,Java 8,Java Stream,为什么我应该在并行流中与collect一起使用并发特征: List<Integer> list = Collections.synchronizedList(new ArrayList<>(Arrays.asList(1, 2, 4))); Map<Integer, Integer> collect = list.stream().parallel() .collect(Collectors.toConcurrentMap(k

为什么我应该在并行流中与collect一起使用并发特征:

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));
列表=
Collections.synchronizedList(新的ArrayList(Arrays.asList(1,2,4));
Map collect=list.stream().parallel()
.collect(收集器.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));
Map collect=list.stream().parallel()
.collect(收集器.toMap(k->k,v->v,(c,c2)->c+c2));
换句话说,不使用此特性有什么副作用,它对内部流操作有用吗?

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

这两个收集器的操作方式完全不同

首先,Stream框架将工作负载分割成可以并行处理的独立块(这就是为什么不需要特殊的集合作为源,
synchronizedList
是不必要的)

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

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


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

如果您大部分都有不同的密钥,那么非并发收集器的合并步骤可能与前一个PUT一样昂贵,破坏了并行处理的任何好处。但是,如果您有许多重复的密钥,需要合并这些值,那么同一密钥上的争用可能会降低并发收集器的性能


因此,没有简单的“哪个更好”的答案(好吧,如果有这样一个答案,为什么还要麻烦添加另一个变体)。这取决于你的实际操作。您可以使用预期场景作为选择一个场景的起点,但应使用实际数据进行度量。因为两者都是等价的,你可以随时改变你的选择。

首先,我对霍尔格的答案给出了+1,这是一个很好的答案。我想简单地说一下:

CONCURRENT->multiple threads在同一个容器中不按特定顺序抛出数据(ConcurrentHashMap)

非并发->多线程组合它们的中间结果

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


这已经有点涵盖了

副作用,包括恶心、数据丢失、地图损坏和其他与并发相关的问题。首先,不要使用
synchronizedList
。没有理由这样做。当然,也没有理由将
数组返回的
列表的内容复制到另一个
ArrayList
@Holger,因为Arrays.asList返回抽象列表,我想用abstractlist进行一些修改和删除,抛出一个异常,所以我将其包装到ArrayList中,当您想
添加
删除
时,您需要一个
数组列表
。但是在这个问题上你没有这样做,所以没有必要。顺便说一句,在这里接受霍尔格的答案怎么样?我不理解你说的,不管收集器的容器是否并发,都将按遇到的顺序顺序收集数据。但是在这里,你说并发收集器支持并发累积,因此,只创建一个ConcurrentMap,所有线程同时累积到该映射中。因此,在完成后,不需要进行合并步骤,因为只有一张地图,所以如何确定收集器的特征?@Amarnatharish是Eugene说的,不是我说的,但仍然是正确的。重要的一点是,
收集器
在声明由并发时是并发的。它不受
供应商返回的实际类型的影响,但始终是程序员有意选择的。在您链接的问答中,Eugene展示了一种声明该特性的方法(使用
Collector.of
)。另一种方法是使用
toConcurrentMap
而不是
toMap