Thread-Sicherheit und Seiteneffekte
Gemeinsam genutzten veränderlichen Zustand vermeiden
Thread-Sicherheit und Seiteneffekte ist eine kostenlose Java Academy-Lektion auf CoddyKit. Dies ist Lektion 3 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.
Parallelität bedeutet nebenläufigen Zugriff
In einem parallelen Stream führen mehrere Threads Ihre Lambdas gleichzeitig aus. Jeder von ihnen berührte gemeinsam genutzte veränderliche Zustand führt zu einer Race Condition.
import java.util.stream.IntStream;
public class Main {
public static void main(String[] args) {
int safe = IntStream.rangeClosed(1, 1000).parallel().sum();
System.out.println(safe);
}
}Die Gefahr: Eine gemeinsam genutzte Variable verändern
Ein einfacher int-Zähler, auf den mehrere Threads zugreifen, verliert Aktualisierungen. Akkumulieren Sie innerhalb von forEach niemals in gemeinsam genutztem veränderlichem Zustand.
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.IntStream;
public class Main {
public static void main(String[] args) {
AtomicInteger counter = new AtomicInteger();
IntStream.rangeClosed(1, 1000).parallel()
.forEach(n -> counter.incrementAndGet());
System.out.println(counter.get());
}
}Warum einfache Collections scheitern
Das Hinzufügen zu einer nicht threadsicheren ArrayList aus einem parallelen forEach kann die Collection beschädigen oder Elemente verlieren. Die Lösung besteht darin, Daten überhaupt nicht gemeinsam zu verwenden und zu verändern.
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class Main {
public static void main(String[] args) {
List<Integer> result = IntStream.rangeClosed(1, 10).parallel()
.boxed()
.collect(Collectors.toList());
System.out.println(result.size());
}
}Das richtige Werkzeug: collect
Verwenden Sie collect statt eines forEach mit Seiteneffekten. Das Collector-Framework sammelt Teilergebnisse sicher threadweise in separaten Containern und führt diese zusammen.
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class Main {
public static void main(String[] args) {
List<Integer> squares = IntStream.rangeClosed(1, 6).parallel()
.map(n -> n * n)
.boxed()
.collect(Collectors.toList());
System.out.println(squares);
}
}Das richtige Werkzeug: reduce
reduce ist von sich aus threadsicher, wenn die Operation assoziativ und zustandslos ist, da Teilergebnisse ohne gemeinsam genutzte Änderungen zusammengeführt werden.
import java.util.stream.IntStream;
public class Main {
public static void main(String[] args) {
int sum = IntStream.rangeClosed(1, 1000).parallel()
.reduce(0, Integer::sum);
System.out.println(sum);
}
}Zustandslose Lambdas
Lambdas in Streams sollten zustandslos sein: Ihr Ergebnis hängt nur von der Eingabe ab, nicht von externen veränderlichen Variablen oder vorherigen Elementen.
import java.util.stream.IntStream;
public class Main {
public static void main(String[] args) {
long count = IntStream.rangeClosed(1, 1000).parallel()
.filter(n -> n % 2 == 0)
.count();
System.out.println(count);
}
}Atomare Typen als Ausweg
Wenn Sie tatsächlich einen Zähler gemeinsam verwenden müssen, nutzen Sie atomare Klassen wie AtomicLong. Sie serialisieren Aktualisierungen korrekt, verursachen allerdings zusätzliche Konkurrenz um die Ressource.
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.IntStream;
public class Main {
public static void main(String[] args) {
AtomicLong sum = new AtomicLong();
IntStream.rangeClosed(1, 1000).parallel()
.forEach(sum::addAndGet);
System.out.println(sum.get());
}
}Nebenläufige Collections
Wenn Sie eine Map zwischen Threads gemeinsam verwenden müssen, ist eine ConcurrentHashMap sicher. Bevorzugen Sie dennoch nach Möglichkeit Collectors.groupingByConcurrent.
import java.util.Map;
import java.util.List;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
List<String> words = List.of("a", "bb", "cc", "ddd");
Map<Integer, List<String>> byLen = words.parallelStream()
.collect(Collectors.groupingByConcurrent(String::length));
System.out.println(byLen);
}
}Von reihenfolgeabhängiger Logik absehen
Verlassen Sie sich innerhalb paralleler Lambdas nicht auf die Reihenfolge der Elemente. Operationen, die eine sequenzielle Verarbeitung voraussetzen, liefern parallel falsche Ergebnisse.
import java.util.stream.IntStream;
public class Main {
public static void main(String[] args) {
int sum = IntStream.rangeClosed(1, 100).parallel()
.map(n -> n * 2)
.sum();
System.out.println(sum);
}
}Reine Funktionen lassen sich sicher parallelisieren
Die wichtigste Regel: Schreiben Sie reine, zustandslose und nicht interferierende Operationen. Dann kann ein Stream sequenziell oder parallel ausgeführt werden und liefert identische, korrekte Ergebnisse.
import java.util.List;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
List<String> in = List.of("red", "green", "blue");
String out = in.parallelStream()
.map(String::toUpperCase)
.sorted()
.collect(Collectors.joining(","));
System.out.println(out);
}
}Keine Eingriffe
Verändern Sie die Quell-Collection nicht, während ein Stream sie verarbeitet. Dies führt unabhängig von sequenzieller oder paralleler Verarbeitung zu einer ConcurrentModificationException oder undefiniertem Verhalten.
import java.util.List;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
List<Integer> src = List.of(1, 2, 3, 4);
List<Integer> doubled = src.parallelStream()
.map(n -> n * 2)
.collect(Collectors.toList());
System.out.println(doubled);
}
}Kurze Prüfung
Wie sollten Ergebnisse aus einem parallelen Stream empfohlen gesammelt werden?
Zusammenfassung
Sie haben parallele Streams korrekt gehalten:
- Gemeinsam genutzter veränderlicher Zustand in parallelen Lambdas verursacht Race Conditions.
- Bevorzugen Sie
collectundreducegegenüber einemforEachmit Seiteneffekten. - Halten Sie Lambdas zustandslos, rein und nicht interferierend.
- Wenn eine gemeinsame Nutzung unvermeidbar ist, verwenden Sie atomare oder nebenläufige Collections – dies sollte jedoch das letzte Mittel sein.
Häufig gestellte Fragen
Ist die Lektion „Thread-Sicherheit und Seiteneffekte“ kostenlos?
Ja — der vollständige Text von „Thread-Sicherheit und Seiteneffekte“ 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 „Thread-Sicherheit und Seiteneffekte“?
Gemeinsam genutzten veränderlichen Zustand vermeiden 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 3 von 4.
Wie lange dauert die Lektion „Thread-Sicherheit und Seiteneffekte“?
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
- Parallele Streams erstellen
- Wann Parallelität hilft
- Thread-Sicherheit und Seiteneffekte
- Häufige Fallstricke