Stellen Sie sich eine vor, List<String> stringList
die mit Java 8- Konstrukten auf viele Arten gedruckt werden kann :
stringList.forEach(System.out::println); // 1) Iterable.forEach
stringList.stream().forEach(System.out::println); // 2) Stream.forEach (order maintained generally but doc does not guarantee)
stringList.stream().forEachOrdered(System.out::println); // 3) Stream.forEachOrdered (order maintained always)
stringList.parallelStream().forEach(System.out::println); // 4) Parallel version of Stream.forEach (order not maintained)
stringList.parallelStream().forEachOrdered(System.out::println); // 5) Parallel version ofStream.forEachOrdered (order maintained always)
Wie unterscheiden sich diese Ansätze voneinander?
Erster Ansatz ( Iterable.forEach
) -
Der Iterator der Sammlung wird im Allgemeinen verwendet und ist ausfallsicher ausgelegt. Dies bedeutet, dass er ausgelöst wird, ConcurrentModificationException
wenn die zugrunde liegende Sammlung während der Iteration strukturell geändert wird. Wie im Dokument erwähnt für ArrayList
:
Eine strukturelle Änderung ist eine Operation, die ein oder mehrere Elemente hinzufügt oder löscht oder die Größe des Hintergrundarrays explizit ändert. Das bloße Festlegen des Werts eines Elements ist keine strukturelle Änderung.
Dies bedeutet, dass das ArrayList.forEach
Einstellen des Werts ohne Probleme zulässig ist. Und im Falle einer gleichzeitigen Erfassung wäre beispielsweise ConcurrentLinkedQueue
der Iterator schwach konsistent, was bedeutet, dass die übergebenen Aktionen ausnahmslos forEach
sogar strukturelle Änderungen vornehmen dürfen ConcurrentModificationException
. Aber hier können die Änderungen in dieser Iteration sichtbar sein oder nicht.
Zweiter Ansatz ( Stream.forEach
) -
Die Reihenfolge ist undefiniert. Es kann zwar nicht für sequentielle Streams auftreten, aber die Spezifikation garantiert dies nicht. Außerdem muss die Aktion nicht störend wirken. Wie in doc erwähnt :
Das Verhalten dieser Operation ist explizit nicht deterministisch. Bei parallelen Stream-Pipelines garantiert diese Operation nicht, dass die Aufeinanderreihenfolge des Streams eingehalten wird, da dies den Vorteil der Parallelität beeinträchtigen würde.
Dritter Ansatz ( Stream.forEachOrdered
) -
Die Aktion wird in der Reihenfolge der Begegnung des Streams ausgeführt. Also, wann immer es auf Bestellung ankommt, forEachOrdered
ohne einen zweiten Gedanken zu verwenden. Wie im Dokument erwähnt :
Führt eine Aktion für jedes Element dieses Streams in der Begegnungsreihenfolge des Streams aus, wenn der Stream eine definierte Begegnungsreihenfolge hat.
Während der Iteration über eine synchronisierte Sammlung würde der erste Ansatz die Sperre der Sammlung einmal aufheben und sie über alle Aufrufe der Aktionsmethode hinweg halten. Bei Streams wird jedoch der Spliterator der Sammlung verwendet, der nicht sperrt und sich auf die bereits festgelegten Regeln von non stützt -Interferenz. Wenn das Sichern der Sammlung während der Iteration geändert wird, wird ein ConcurrentModificationException
Wert ausgelöst oder es kann zu einem inkonsistenten Ergebnis kommen.
Vierter Ansatz (parallel Stream.forEach
) -
Wie bereits erwähnt, keine Garantie, die bei parallelen Streams erwartete Reihenfolge der Begegnungen einzuhalten. Es ist möglich, dass Aktionen in verschiedenen Threads für verschiedene Elemente ausgeführt werden, was bei niemals der Fall sein kann forEachOrdered
.
Fünfter Ansatz (parallel Stream.forEachOrdered
) -
Der forEachOrdered
verarbeitet die Elemente in der von der Quelle angegebenen Reihenfolge, unabhängig davon, ob der Stream sequentiell oder parallel ist. Es macht also keinen Sinn, dies mit parallelen Streams zu verwenden.
List
? Zeigen Sie uns, wie Sie es deklariert und instanziiert haben.