Parallele Streams: Leistung und Fallstricke
Aktivieren Sie parallele Streams, verstehen Sie den gemeinsamen Thread-Pool und vermeiden Sie Fehler durch gemeinsam genutzten veränderlichen Zustand.
Parallele Streams: Leistung und Fallstricke ist eine kostenlose Java Academy-Lektion auf CoddyKit. Dies ist Lektion 2 von 4. Du kannst die komplette Lektion unten kostenlos lesen – dann übst du sie direkt im Browser mit einem integrierten Code-Editor und einem KI-Tutor rund um die Uhr. Sie ist Teil des Java Academy-Lernpfads, und dein Fortschritt wird über Web und CoddyKit-App synchronisiert. Der Java Academy-Kurs umfasst insgesamt 4 Lektionen.
Parallele Streams aktivieren
Rufen Sie für einen beliebigen Stream .parallel() auf oder verwenden Sie Collection.parallelStream(). Die Stream-Operationen werden im gemeinsamen ForkJoinPool ausgeführt (Standard: CPU-Kerne minus 1 Threads).
long count = list.parallelStream()
.filter(n -> n % 2 == 0)
.count();
System.out.println(count);Wann sich Parallelisierung lohnt
Parallele Streams lohnen sich, wenn der Datensatz groß ist (100.000 oder mehr Elemente), die Verarbeitung jedes Elements rechenintensiv ist und die Pipeline zustandslos sowie unabhängig von der Reihenfolge ist.
// Good candidate: CPU-heavy computation on large dataset
long sum = LongStream.rangeClosed(1, 10_000_000)
.parallel()
.filter(n -> isPrime(n))
.sum();
System.out.println(sum);Wann Sie keine parallelen Streams verwenden sollten
Vermeiden Sie parallele Streams bei kleinen Collections, E/A-gebundenen Operationen (sie blockieren Threads des ForkJoinPool), zustandsbehafteten Operationen (Sortieren, distinct) oder wenn die Reihenfolge wichtig und nur mit hohem Aufwand wiederherzustellen ist.
// Bad: I/O bound — blocking ForkJoinPool threads starves other tasks
List<String> result = urls.parallelStream()
.map(url -> httpGet(url)) // BLOCKS the common pool
.collect(Collectors.toList());Fehler durch gemeinsam genutzten veränderlichen Zustand
Parallele Streams führen Operationen gleichzeitig in mehreren Threads aus. Wenn Sie eine gemeinsam genutzte veränderliche Collection ändern, führt dies zu Datenrennen und falschen Ergebnissen.
// RACE CONDITION — never do this:
List<Integer> results = new ArrayList<>(); // not thread-safe
numbers.parallelStream().forEach(n -> results.add(n)); // corrupts list!
// Fix:
List<Integer> safe = numbers.parallelStream().collect(Collectors.toList());Zustandsbehaftete Operationen: sorted und distinct
sorted() und distinct() müssen alle Elemente sehen, bevor sie eine Ausgabe erzeugen können. Das begrenzt die Parallelisierung und macht die parallele Verarbeitung oft langsamer als die sequenzielle.
// sorted() forces collect-all, then sort — parallel overhead usually not worth it:
list.parallelStream().sorted().collect(Collectors.toList());Reihenfolgeabhängige Operationen
findFirst() und forEachOrdered() bewahren bei parallelen Streams die Begegnungsreihenfolge, was zusätzlichen Synchronisationsaufwand verursacht. Verwenden Sie findAny() oder forEach(), wenn die Reihenfolge keine Rolle spielt.
// Faster in parallel (order-insensitive):
Optional<Integer> any = list.parallelStream().filter(n -> n > 10).findAny();
// Slower in parallel (must preserve order):
Optional<Integer> first = list.parallelStream().filter(n -> n > 10).findFirst();Größe des Thread-Pools festlegen
Der gemeinsame ForkJoinPool verwendet Runtime.getRuntime().availableProcessors() - 1 Threads. Führen Sie parallele Streams in einem benutzerdefinierten Pool aus, indem Sie sie innerhalb eines Aufrufs von ForkJoinPool.invoke() übergeben.
ForkJoinPool custom = new ForkJoinPool(8);
long result = custom.submit(
() -> LongStream.rangeClosed(1, 1_000_000).parallel().sum()
).get();Parallele und sequenzielle Verarbeitung benchmarken
Führen Sie Benchmarks immer mit JMH und realistischen Datensatzgrößen durch. Der Overhead der Parallelisierung (Aufteilen, Thread-Koordination, Zusammenführen) ist nur dann gerechtfertigt, wenn die Berechnungszeit den Overhead deutlich übersteigt.
@Benchmark
public long sequential() { return LongStream.rangeClosed(1,1_000_000).sum(); }
@Benchmark
public long parallel() { return LongStream.rangeClosed(1,1_000_000).parallel().sum(); }Reduktionsoperationen mit Parallelisierung
reduce() und collect() sind dafür ausgelegt, bei paralleler Verarbeitung korrekt zu funktionieren, wenn die Operationen assoziativ sind und der Identitätswert korrekt ist.
// Associative reduce — safe in parallel:
int sum = list.parallelStream().reduce(0, Integer::sum);
// Non-associative: subtraction — NOT safe in parallel:
int bad = list.parallelStream().reduce(0, (a, b) -> a - b); // wrong result!Teilbarkeit ist entscheidend
Parallele Streams teilen die Datenquelle mithilfe eines Spliterator auf. ArrayList und Arrays lassen sich in O(1) aufteilen; LinkedList und HashSet lassen sich schlecht aufteilen, wodurch die Effizienz der Parallelisierung sinkt.
Zusammenfassung: Checkliste für parallele Streams
Prüfen Sie vor der Verwendung paralleler Streams: (1) großer Datensatz, (2) CPU-gebundene Operationen, (3) kein gemeinsam genutzter veränderlicher Zustand, (4) Reihenfolge unwichtig, (5) teilbare Quelle (Array oder ArrayList). Führen Sie im Zweifel einen Benchmark durch.
Kurze Überprüfung
Was geschieht, wenn Sie in einem parallelen Stream innerhalb von forEach Elemente zu einer nicht threadsicheren Collection hinzufügen?
Zusammenfassung
Parallele Streams verwenden den ForkJoinPool. Sie eignen sich für große, CPU-gebundene und reihenfolgeunabhängige Pipelines ohne Zustandsabhängigkeit. Verändern Sie niemals gemeinsam genutzten Zustand. Führen Sie vor der Entscheidung einen Benchmark durch – bei kleinen Datenmengen ist die parallele Verarbeitung oft langsamer.
Häufig gestellte Fragen
Ist die Lektion „Parallele Streams: Leistung und Fallstricke“ kostenlos?
Ja — der vollständige Text von „Parallele Streams: Leistung und Fallstricke“ ist hier im Web kostenlos zu lesen. Um sie interaktiv zu üben (integrierter Code-Editor und 24/7 KI-Tutor) und den Rest des Java Academy-Kurses freizuschalten, upgrade auf CoddyKit PRO. Der Java Academy-Kurs umfasst insgesamt 4 Lektionen.
Was lerne ich in „Parallele Streams: Leistung und Fallstricke“?
Aktivieren Sie parallele Streams, verstehen Sie den gemeinsamen Thread-Pool und vermeiden Sie Fehler durch gemeinsam genutzten veränderlichen Zustand. Du übst Java Academy mit praktischem Code, den du direkt im Browser ausführst, und ein 24/7 KI-Tutor beantwortet deine Fragen während du die Lektion bearbeitest.
Brauche ich Erfahrung, um Java Academy zu starten?
Keine Vorkenntnisse erforderlich. Java Academy auf CoddyKit ist für Anfänger bis fortgeschrittene Lernende strukturiert, sodass du hier starten oder von Anfang an beginnen und in deinem eigenen Tempo voranschreiten kannst. Dies ist Lektion 2 von 4.
Wie lange dauert die Lektion „Parallele Streams: Leistung und Fallstricke“?
Die meisten CoddyKit-Lektionen dauern etwa 5–10 Minuten. Jede ist kompakt und interaktiv, sodass du stetig Fortschritte machst und genau dort weitermachst, wo du aufgehört hast – im Web und in der App.
Kann ich in dieser Java Academy-Lektion Code schreiben und ausführen?
Ja. Jede Java Academy-Lektion enthält einen integrierten Code-Editor, sodass du echten Code direkt in deinem Browser schreibst und ausführst und sofort KI-Feedback erhältst — ohne lokale Einrichtung erforderlich.
Alle Lektionen in diesem Kurs
- flatMap für verschachtelte Collections
- Parallele Streams: Leistung und Fallstricke
- Spliterator: Aufteilen für Parallelität
- Unendliche Streams mit iterate und generate