Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ArrayList filled by parallel stream contains nulls

I'm creating a list and fill it from an other list using parallel stream, unexpectedly destination list contains nulls. It happens seldom and inconstantly. Does someone have the same issue?

Here is the piece of code:

Collection<DestinationObj> DestinationObjList = Lists.newArrayList();
SourceObjList.parallelStream().forEach(portalRule -> DestinationObjList.add(new DestinationObj(portalRule)));
return DestinationObjList;
like image 626
Pavel Leonov Avatar asked Oct 18 '25 15:10

Pavel Leonov


2 Answers

You should collect in parallel in a bit different way:

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

The problem you are having is that ArrayList is not thread-safe and as such the result is really un-defined. Notice that using a parallel stream does not require a thread-safe collection - Lists::newArrayList is not.

like image 142
Eugene Avatar answered Oct 20 '25 06:10

Eugene


Using a collector to synchronize access to the destination list gives you a performance penalty in the synchronization. In fact, you can do the same thing without synchronization, since you know the size of the source list and can therefore create a destination list of the required size from the start.

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);

EDIT: Just putting Holger's improvement here for clarity:

List<DestinationObj> destinationObjList = Arrays.asList(
        sourceObjList
            .parallelStream()
            .map(DestinationObj::new)
            .toArray(DestinationObj[]::new));
like image 31
Klitos Kyriacou Avatar answered Oct 20 '25 06:10

Klitos Kyriacou