Java Fortgeschrittenen Schulung – Kursbezug
Ziel dieses Kapitels: Sie führen Aufgaben parallel aus (ExecutorService), koordinieren Ergebnisse (Future/CompletableFuture) und schützen kritische Bereiche korrekt.
Worum geht’s?
- Threads vs. Executors: Pools sind kontrollierbar (Ressourcen, Queue, Shutdown).
- Shared State: Race Conditions, Visibility, atomare Updates.
- Synchronisation: synchronized / Lock / atomare Klassen.
- Koordination: Future, CompletableFuture, Timeouts, sauberes Beenden.
Lehr-/Lernziele
- (LZ1) parallele Aufgaben mit ExecutorService ausführen und korrekt beenden (shutdown/awaitTermination).
- (LZ2) Race Conditions erkennen und Shared State sicher machen (synchronized/Lock/Atomic).
- (LZ3) Ergebnisse/Fehler koordinieren (Future/CompletableFuture, Timeouts, Aggregation).
ExecutorService: kontrollierte Parallelität
import java.util.concurrent.*;
public class KursReportPoolDemo {
public static void main(String[] args) throws Exception {
ExecutorService pool = Executors.newFixedThreadPool(4);
Future<String> trainerInfo = pool.submit(() -> {
Thread.sleep(120);
return "Trainer: verfügbar";
});
Future<String> auslastung = pool.submit(() -> {
Thread.sleep(180);
return "Auslastung: 80%"; // Demo
});
System.out.println("Report-Teil 1: " + trainerInfo.get());
System.out.println("Report-Teil 2: " + auslastung.get());
pool.shutdown();
if(!pool.awaitTermination(2, TimeUnit.SECONDS)) {
pool.shutdownNow();
}
}
}Race Condition: gemeinsamer Zähler
import java.util.concurrent.*;
public class KursCheckRace {
static int fertig = 0;
public static void main(String[] args) throws Exception {
ExecutorService pool = Executors.newFixedThreadPool(8);
for(int i=0;i<1000;i++){
pool.submit(() -> fertig++); // NICHT thread-safe
}
pool.shutdown();
pool.awaitTermination(2, TimeUnit.SECONDS);
System.out.println("fertig=" + fertig); // oft < 1000
}
}import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class KursCheckSafe {
static AtomicInteger fertig = new AtomicInteger(0);
public static void main(String[] args) throws Exception {
ExecutorService pool = Executors.newFixedThreadPool(8);
for(int i=0;i<1000;i++){
pool.submit(() -> fertig.incrementAndGet());
}
pool.shutdown();
pool.awaitTermination(2, TimeUnit.SECONDS);
System.out.println("fertig=" + fertig.get()); // 1000
}
}Kritischer Bereich: Lock / synchronized
import java.util.concurrent.locks.ReentrantLock;
public class KursReportStatus {
private int ok = 0;
private int fehler = 0;
private final ReentrantLock lock = new ReentrantLock();
public void markOk() {
lock.lock();
try { ok++; }
finally { lock.unlock(); }
}
public void markFehler() {
lock.lock();
try { fehler++; }
finally { lock.unlock(); }
}
public String summary() {
return "OK=" + ok + " | Fehler=" + fehler;
}
}CompletableFuture: parallel laden, dann aggregieren
import java.util.concurrent.*;
public class DashboardReportDemo {
public static void main(String[] args) {
CompletableFuture<Integer> buchungen =
CompletableFuture.supplyAsync(() -> slow(200, 12));
CompletableFuture<Double> umsatz =
CompletableFuture.supplyAsync(() -> slow(350, 2499.0));
CompletableFuture<String> auslastung =
CompletableFuture.supplyAsync(() -> slow(250, "80%")); // demo
CompletableFuture<String> report =
CompletableFuture.allOf(buchungen, umsatz, auslastung)
.orTimeout(1, TimeUnit.SECONDS)
.thenApply(v ->
"Kursreport: Buchungen=" + buchungen.join()
+ " | Umsatz=" + umsatz.join()
+ " | Auslastung=" + auslastung.join()
)
.exceptionally(ex ->
"Kursreport nicht verfügbar: " + ex.getClass().getSimpleName()
);
System.out.println(report.join());
}
static <T> T slow(int ms, T value) {
try { Thread.sleep(ms); } catch (InterruptedException ignored) {}
return value;
}
}Deadlocks vermeiden (Merksatz)
- Locks immer in gleicher Reihenfolge anfordern (A → B, nie B → A).
- Bevorzugen: immutable Daten / Ergebnisse sammeln, statt shared mutable state.
- Bei Locks:
tryLock+ Timeout kann Notbremse sein, aber Architektur ist wichtiger.
Praxisaufgabe (Mini)
- Erstellen Sie einen
ExecutorServicemit 4 Threads. - Starten Sie 20 Tasks: jeder Task simuliert einen „Kurs-Datencheck“ (OK/Fehler + Meldung).
- Sammeln Sie Ergebnisse ein (Timeout pro Task).
- Zählen Sie OK/Fehler und geben Sie Fehlerdetails aus.
- Beenden Sie den Pool sauber (shutdown/awaitTermination).
Lösung anzeigen
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class KursBatchCheck {
// Ergebnis-Typ (Java 16+ Record; sonst: normale Klasse)
public record CheckErgebnis(int kursNr, boolean ok, String meldung) {}
// Simulierter Check (z.B. Datenvollständigkeit/Plätze/Trainer)
static CheckErgebnis pruefe(int kursNr) {
try { Thread.sleep(50); } catch (InterruptedException ignored) {}
// Demo-Regel: jeder 5. Kurs ist "auffällig"
boolean ok = (kursNr % 5 != 0);
String msg = ok
? "OK: Kursdaten plausibel"
: "FEHLER: Trainer fehlt oder Plätze unplausibel";
return new CheckErgebnis(kursNr, ok, msg);
}
public static void main(String[] args) throws Exception {
ExecutorService pool = Executors.newFixedThreadPool(4);
List<Future<CheckErgebnis>> futures = new ArrayList<>();
AtomicInteger fertig = new AtomicInteger(0);
for (int i = 1; i <= 20; i++) {
final int nr = i;
futures.add(pool.submit(() -> {
try {
return pruefe(nr);
} finally {
fertig.incrementAndGet();
}
}));
}
int okCount = 0;
int fehlerCount = 0;
List<String> fehlerDetails = new ArrayList<>();
for (Future<CheckErgebnis> f : futures) {
try {
CheckErgebnis erg = f.get(300, TimeUnit.MILLISECONDS);
if (erg.ok()) okCount++;
else {
fehlerCount++;
fehlerDetails.add("Kurs " + erg.kursNr() + ": " + erg.meldung());
}
} catch (TimeoutException ex) {
fehlerCount++;
fehlerDetails.add("Timeout: Ein Check dauerte zu lange");
}
}
pool.shutdown();
if (!pool.awaitTermination(2, TimeUnit.SECONDS)) {
pool.shutdownNow();
}
System.out.println("Fertig: " + fertig.get() + "/20");
System.out.println("OK: " + okCount + " | Fehler: " + fehlerCount);
if (!fehlerDetails.isEmpty()) {
System.out.println("\nFehlerdetails:");
for (String m : fehlerDetails) System.out.println("- " + m);
}
}
}
Optional: typische Stolperfallen
- Pool nie beendet:
shutdown()+awaitTermination()nutzen. - Shared State ohne Schutz: counter++ ist nicht atomar → Atomic/Lock/synchronized.
- Blockieren im Pool: lange blockierende Calls „verstopfen“ Threads → Timeouts/Design prüfen.
- Zu viele Threads: mehr Threads ≠ schneller (Overhead, Context Switch).
Kurz-Takeaways
- Executors statt rohe Threads, immer Shutdown.
- Shared mutable state vermeiden oder schützen (Atomic/Lock/synchronized).
- Future/CompletableFuture für Koordination, Timeouts und Aggregation.