2017-04-05 50 views
2

我创建一个列表并使用并行流从其他列表中填充它,意外的是目标列表包含空值。它很少发生,不稳定。有人有同样的问题吗?由并行流填充的ArrayList包含空值

下面是一段代码:

Collection<DestinationObj> DestinationObjList = Lists.newArrayList(); 
SourceObjList.parallelStream().forEach(portalRule -> DestinationObjList.add(new DestinationObj(portalRule))); 
return DestinationObjList; 
+4

你'ArrayList'不是线程安全的。 – Flown

+4

https://docs.oracle.com/javase/tutorial/collections/streams/parallelism.html请参阅页面末尾;) – 2017-04-05 11:55:38

+2

顺便说一句,您应该使用'.collect(...)'而不是执行' .forEach'。我怀疑它可能适用于并行流,即使没有线程安全列表(因为收集器将处理同步),但是您必须验证它。 –

回答

3

你应该收集并联有点不同的方式:

SourceObjList.parallelStream() 
     .map(DestinationObj::new) 
     .collect(Collectors.toCollection(ArrayList::new)); 

您有问题是ArrayList不线程安全的,因此结果真的没有定义。 注意,使用并行流不需要线程安全集合 - Lists::newArrayList不需要。

+1

顺便说一下,我不会忽略结果的DestinationObjList =赋值,因为该模式不同于OP的'forEach'方法从根本上说,所以我们不应该将其视为隐含的。 – Holger

2

使用收集器同步对目标列表的访问会给您在同步中的性能损失。实际上,您可以在没有同步的情况下执行相同的操作,因为您知道源列表的大小,因此可以从一开始就创建所需大小的目标列表。

DestinationObj[] dest = new DestinationObj[sourceObjList.size()]; 
IntStream.range(0, sourceObjList.size()) 
    .parallel() 
    .forEach(i -> dest[i] = new DestinationObj(sourceObjList.get(i))); 
List<DestinationObj> destinationObjList = Arrays.asList(dest); 

编辑:只是把霍尔格的改进这里为清楚:

List<DestinationObj> destinationObjList = Arrays.asList(
     sourceObjList 
      .parallelStream() 
      .map(DestinationObj::new) 
      .toArray(DestinationObj[]::new)); 
+3

在这种情况下,数组方法可能比收集器稍微高效一些,但这并不意味着您必须手工完成,'DestinationObj [] dest = sourceObjList.parallelStream().map(DestinationObj :: new) .toArray(DestinationObj [] :: new)'会做同样的事情,同样可以利用已知的结果大小,但如果以不再可预测大小的方式更改操作,仍然可以继续工作。它也允许在没有临时变量的情况下执行:'List l = Arrays.asList(sourceObjList.parallelStream().map(DestinationObj :: new).toArray(DestinationObj [] :: new));' – Holger

+0

好点,@霍尔。在源码流已知大小的情况下,'.toArray'是否保证不添加同步?在使用'collect(Collectors.toCollection(ArrayList :: new))'的时候也应该可以避免同步,就像Eugene的回答一样。它可以创建一个适当大小的ArrayList,并使用'add(index,element)'从而消除同步的需要。在这种情况下,我的答案是毫无意义的。 –

+2

“收集器”不能通过同步工作。它通过使用['Supplier'](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Collector.html#supplier--)创建线程本地容器执行孤立的部分评估。在这个特定的例子中,它意味着每个线程都会将一部分数据收集到自己的ArrayList中,然后部分结果必须通过[组合函数](https://docs.oracle)合并。 com/javase/8/docs/api/java/util/stream/Collector.html#combiner--),这相当于'addAll' ... – Holger