Alle Tabs der Lerneinheit (Erklärung · Interaktiv verstehen · Praxis-Übung · Klausur-Quiz) als durchgehender Text. Ideal zum Wiederholen vor der Klausur, und für Suchmaschinen wie Google, Bing und KI-Suche (ChatGPT, Perplexity).
Diese Lerneinheit wurde für typische Bachelor-Klausuren konzipiert. So prüfen wir · Fehler entdeckt? Melde ihn uns oder markiere die fragliche Stelle direkt im Text oben.
Alle Tabs der Lerneinheit (Erklärung · Interaktiv verstehen · Praxis-Übung · Klausur-Quiz) als durchgehender Text. Ideal zum Wiederholen vor der Klausur, und für Suchmaschinen wie Google, Bing und KI-Suche (ChatGPT, Perplexity).
Ein Programm macht normalerweise eine Sache nach der anderen. Mit Threads laufen mehrere Abläufe scheinbar gleichzeitig: ein Download im Hintergrund, während die UI reagiert; mehrere Anfragen parallel bedient. Das ist mächtig, aber sobald sich zwei Threads denselben Speicher teilen, entstehen Fehler, die es im Single-Thread-Code nicht gibt: Race Conditions. Genau die sind der Prüfungsschwerpunkt in Programmieren 2.
Was du können musst:
Runnable, start() vs. run(), join())counter++ bei mehreren Threads kaputtgeht (Race Condition)synchronized beheben (gegenseitiger Ausschluss)volatile von synchronized abgrenzen (Sichtbarkeit vs. Atomarität)Abgrenzung: hier geht es um die Grundlagen (Thread, synchronized, Race Condition, Deadlock). Tiefe Nebenläufigkeit (
java.util.concurrent, Fork/Join, das formale Speichermodell) ist Master-Stoff.
Klausur-Tipp: Wenn eine Aufgabe fragt "wie viele Threads laufen?" oder "in welchem Thread läuft das?", suche nach start() (neuer Thread) versus run() (selber Thread). Und: ohne join() ist die Ausgabe-Reihenfolge mehrerer Threads nicht vorhersagbar.
Anmelden, um den Fortschritt zu speichern.
Nächster Schritt
Aktives Abrufen festigt Wissen schneller als nochmal lesen.
Ein Programm macht normalerweise eine Sache nach der anderen. Mit Threads laufen mehrere Abläufe scheinbar gleichzeitig: ein Download im Hintergrund, während die UI reagiert; mehrere Anfragen parallel bedient. Das ist mächtig, aber sobald sich zwei Threads denselben Speicher teilen, entstehen Fehler, die es im Single-Thread-Code nicht gibt: Race Conditions. Genau die sind der Prüfungsschwerpunkt in Programmieren 2.
Was du können musst:
Runnable, start() vs. run(), join())counter++ bei mehreren Threads kaputtgeht (Race Condition)synchronized beheben (gegenseitiger Ausschluss)volatile von synchronized abgrenzen (Sichtbarkeit vs. Atomarität)Abgrenzung: hier geht es um die Grundlagen (Thread, synchronized, Race Condition, Deadlock). Tiefe Nebenläufigkeit (
java.util.concurrent, Fork/Join, das formale Speichermodell) ist Master-Stoff.
Ein Thread ist ein eigenständiger Ausführungsfaden innerhalb eines Programms. Mehrere Threads teilen sich denselben Speicher, deshalb muss der Zugriff auf gemeinsamen veränderlichen Zustand koordiniert werden, sonst entstehen Race Conditions.
// Runnable (funktionales Interface) ist der bevorzugte Weg
Runnable aufgabe = () -> System.out.println("Laeuft in " + Thread.currentThread().getName());
Thread t = new Thread(aufgabe);
t.start(); // startet einen NEUEN Thread, der run() ausfuehrt
t.join(); // wartet, bis t fertig ist
start() vs. run() ist die häufigste Anfänger-Falle: t.start() erzeugt einen neuen Thread. t.run() ruft die Methode nur direkt im aktuellen Thread auf, also gar kein zweiter Thread. join() lässt den aufrufenden Thread warten, bis der andere fertig ist.
counter++ sieht aus wie eine Operation, ist aber in Wahrheit drei: den Wert lesen, plus eins rechnen, zurückschreiben (read-modify-write). Wenn sich zwei Threads dabei verschränken, lesen beide denselben alten Wert, und ein Update geht verloren.
class Zaehler {
int counter = 0;
void inc() { counter++; } // NICHT atomar!
}
// Zwei Threads rufen je 1x inc() auf. Erwartet: 2. Tatsaechlich: manchmal 1.
Im Stepper siehst du genau, wie ein Update verloren geht.
Interaktive Visualisierung
Interaktive Komponente: probiere sie im Topic-Player oben aus.
synchronized sorgt für gegenseitigen Ausschluss (mutual exclusion): immer nur ein Thread ist im geschützten Block, die anderen warten. Java nutzt dafür einen intrinsischen Lock (das Objekt selbst bzw. this).
class Zaehler {
private int counter = 0;
synchronized void inc() { counter++; } // jetzt atomar pro Aufruf
// oder: void inc() { synchronized (this) { counter++; } }
}
Damit wird der read-modify-write unteilbar: Thread B kommt erst rein, wenn A komplett fertig ist, und liest dann A's aktuelles Ergebnis.
Interaktive Visualisierung
Interaktive Komponente: probiere sie im Topic-Player oben aus.
volatile garantiert Sichtbarkeit: schreibt ein Thread ein volatile-Feld, sehen andere Threads den neuen Wert sofort (kein veralteter Cache-Wert). Aber volatile macht NICHT atomar, für counter++ reicht es nicht (das bleibt read-modify-write). Für atomares Zählen: synchronized oder AtomicInteger.
Deadlock: Zwei Threads halten je einen Lock und warten auf den jeweils anderen, beide blockieren für immer.
// Thread 1: synchronized(A) { synchronized(B) { ... } }
// Thread 2: synchronized(B) { synchronized(A) { ... } } // umgekehrte Reihenfolge!
Lösung: Locks immer in derselben Reihenfolge anfordern.
1. counter++ ist nicht atomar. Es ist read-modify-write (3 Schritte). Gemeinsamer veränderlicher Zustand plus mehrere Threads ergibt eine Race Condition.
2. synchronized = gegenseitiger Ausschluss. Nur ein Thread im Block oder in der Methode, über einen intrinsischen Lock (this bzw. das angegebene Objekt).
3. start() startet einen Thread, run() nicht. t.start() erzeugt einen neuen Thread (der run() ausführt). t.run() ist nur ein normaler Methodenaufruf im selben Thread.
4. join() wartet auf das Ende. Ohne join() läuft der aufrufende Thread (oft main) womöglich weiter, bevor die anderen fertig sind.
5. volatile sichert Sichtbarkeit, nicht Atomarität. Für sichtbaren, aber zusammengesetzten Zugriff (counter++) braucht es synchronized oder AtomicInteger.
6. Deadlock durch gegenläufige Lock-Reihenfolge. Zwei Threads, zwei Locks, umgekehrt angefordert, beide warten ewig. Vorbeugung: konsistente Lock-Reihenfolge.
1. t.run() statt t.start(). Startet keinen neuen Thread, alles läuft im aufrufenden Thread. Klassische Klausur-Falle.
2. Race Condition als "passiert schon nicht" abtun. Sie ist nicht-deterministisch: läuft tausendmal gut, dann unter Last plötzlich falsch. Schwer reproduzierbar, deshalb gefährlich.
3. volatile als Lock missverstehen. Es schützt nicht vor Race Conditions bei counter++, nur vor veralteten Werten. Atomarität braucht synchronized/Atomic*.
4. Über-Synchronisierung. Alles in einen großen synchronized-Block packen serialisiert komplett, dann hätte man auch single-threaded bleiben können. Nur den kritischen Abschnitt schützen.
5. Reihenfolge der Threads annehmen. Es gibt keine Garantie, welcher Thread wann läuft, das entscheidet der Scheduler. Code darf nicht von einer bestimmten Reihenfolge abhängen.
6. Vergessen, dass main auch ein Thread ist. Ohne join() kann main enden oder weiterlaufen, bevor die gestarteten Threads ihr Ergebnis geschrieben haben.
Derselbe Thread, zwei Aufrufe. Einer startet echte Nebenläufigkeit, der andere nicht. Achte im Stepper auf den Namen des ausführenden Threads in der Ausgabe, das verrät, ob ein neuer Thread entstanden ist.
Interaktive Visualisierung
Interaktive Komponente: probiere sie im Topic-Player oben aus.
Klausur-Tipp: Wenn eine Aufgabe fragt "wie viele Threads laufen?" oder "in welchem Thread läuft das?", suche nach start() (neuer Thread) versus run() (selber Thread). Und: ohne join() ist die Ausgabe-Reihenfolge mehrerer Threads nicht vorhersagbar.
Klausurfragen mit Lösungen (6)
Antwort: t.start()
Erklärung: t.start() erzeugt einen neuen Thread und ruft dort run() auf. t.run() ruft die Methode nur direkt im aktuellen Thread auf (kein neuer Thread). execute() und go() gibt es bei Thread nicht.
Antwort: Falsch
Erklärung: Falsch. counter++ ist read-modify-write (lesen, plus eins, schreiben), also drei Schritte. Zwei Threads koennen sich dazwischen verschraenken und ein Update verlieren. Das ist die klassische Race Condition.
Typ: Wahr/Falsch
Antwort: Irgendetwas zwischen 1000 und 2000, nicht-deterministisch
Erklärung: Ohne Synchronisierung gehen durch Race Conditions Updates verloren. Der Endwert liegt irgendwo zwischen 1000 (sehr viele verloren) und 2000 (Idealfall), und ist von Lauf zu Lauf verschieden. Genau diese Nicht-Determiniertheit macht den Bug gefaehrlich.
Antwort: Nur ein Thread kann die Methode (auf demselben Objekt) gleichzeitig ausführen
Erklärung: synchronized erzwingt gegenseitigen Ausschluss ueber den intrinsischen Lock des Objekts: nur ein Thread ist gleichzeitig drin, die anderen warten. Es macht NICHT parallel (im Gegenteil, es serialisiert) und nicht schneller.
Zuordnungen:
Erklärung: synchronized = Atomarität/Ausschluss, volatile = Sichtbarkeit (nicht Atomarität), join = auf Thread-Ende warten, start = neuen Thread erzeugen.
Typ: Zuordnung
Antwort: volatile sichert nur die Sichtbarkeit, aber counter++ bleibt read-modify-write und damit nicht atomar
Erklärung: volatile garantiert, dass Threads den aktuellen Wert sehen (keine veralteten Caches), aber es macht den zusammengesetzten read-modify-write von counter++ nicht unteilbar. Zwei Threads koennen weiterhin denselben Wert lesen. Loesung: synchronized oder AtomicInteger.
Klausurfragen mit Lösungen (6)
Thread t = new Thread(() ->
System.out.println(Thread.currentThread().getName()));
t.run();Antwort: main
Erklärung: t.run() ruft run() direkt im aktuellen Thread auf (main), es entsteht kein neuer Thread. Ausgabe: main. Erst t.start() wuerde einen neuen Thread (Thread-0) erzeugen.
Antwort: Wenn das Ergebnis von der zeitlichen Verschränkung mehrerer Threads abhängt und dadurch falsch werden kann
Erklärung: Eine Race Condition liegt vor, wenn das Ergebnis von der (nicht kontrollierbaren) zeitlichen Verschraenkung der Threads abhaengt, typisch beim ungeschuetzten Zugriff auf gemeinsamen veraenderlichen Zustand (z.B. counter++).
Antwort: Falsch
Erklärung: Falsch. synchronized laesst nur EINEN Thread gleichzeitig in den Block, die anderen warten. Es serialisiert also und macht an dieser Stelle eher langsamer. Der Zweck ist Korrektheit (kein Lost Update), nicht Geschwindigkeit.
Typ: Wahr/Falsch
Lösungen pro Lücke:
Erklärung: read-modify-write, synchronized (Ausschluss/Atomarität), volatile (Sichtbarkeit, nicht Atomarität). Das Standard-Vokabular der Nebenläufigkeits-Grundlagen.
Typ: Lückentext
Antwort: Es lässt den aufrufenden Thread warten, bis t fertig ist
Erklärung: join() blockiert den aufrufenden Thread, bis t durchgelaufen ist. Ohne join() koennte main weiterlaufen oder enden, bevor t sein Ergebnis geschrieben hat.
Antwort: Deadlock: jeder hält einen Lock und wartet auf den anderen
Erklärung: Klassischer Deadlock: Thread 1 haelt a und will b, Thread 2 haelt b und will a. Beide warten ewig. Vorbeugung: Locks immer in derselben Reihenfolge anfordern (erst a, dann b, in beiden Threads).