Gefahren der Nebenläufigkeit
Race Condition
Ungenügend synchronisierte Zugriffe auf gemeinsame Ressourcen
- Je nach Thread-Verzahnung und Timing unterschiedliche Ergebnisse
- Oft ist die Ursache ein Data Race
- Es gibt auch Race Conditions ohne Data Race
- oder Data Race ohne Race Condition
- Programm funktioniert dann zwar, ist aber formal falsch
Data Race
- Mehrere Threads greifen auf selben Speicher (Var, Array, ...) zu
- Mindestens ein Write-Zugriff von einem Thread ist involviert
- Ist ein formaler Fehler: Einfacher zu erkennen als generelle Race Condition (z.B. nicht-atomare Operationen)
Synchronisation
Verzichtbare Fälle
- Wenn Objekte nur gelesen werden (immutable Objects)
- Confinement: Object gehört zu einer Zeit nur einem Thread
- Thread Confinement: Objekt lebt nur bei einem Thread ("privat")
- Object Confinement: Einkapsung in synchronisiertes Objekt, Zugriff auf Objekt nur mit synchronisierten Methoden
Thread Confinement
new Thread(() -> { OutputStream output = new FileOutputStream(); try { doService(output); } finally { output.close(); } }).start();
Kapselungsbrüche
- Inneres Objekt von aussen zugreifbar
- Rückgabe auf Referenz eines inneren Objekts
- Eine innere Referenz wird irgendwo rein "installiert", z.B. in eine Liste, die per Parameter mitgegeben wird
- Inneres Objekt gibt selber
this
raus
Thread-Safety
- Keine klare Definition
- Heisst Klassen und Methoden, die für sich intern synchronisiert sind
- Keine Synchronisierung über mehrere Methoden -> Race Conditions immer noch möglich
- Immer Spezifikation prüfen, was thread-safe ist
- Synchronisation reicht nicht immer, Iteration ist z.B. nicht synchronisiert
Java Collections
- Alte Collections
Vector
,Stack
undHashtable
sind thread-safe - Moderne Collections nicht! (
HashSet
,ArrayList
,HashMap
, ...)- Grund: Synchronisation ist teuer! Thread-Safety wird meist nicht benötigt
- Eigene
ConcurrentCollections
sind thread-safe:ConcurrentHashMap
,ConcurrentLinkedQueue
, ... - Iteration ist "schwach konsistent": Es gibt keine Garantie, dass alle Updates gelesen werden
Deadlocks
Gegenseitiges Aussperren von Threads - Einige Threads sperren sich gegenseitig so, dass keiner von denen weitermachen kann - Vorsicht vor impliziten nested Locks! Wenn z.B. eine synchronisierte Methode eine andere synchronisierte Methode aufruft - Programm mit potentiellem Deadlock ist inkorrekt - Livelocks: Deadlocks, die ständig eine Bedingung prüfen, also weiterhin CPU verbrauchen
Deadlocks erkennen
- Wenn Deadlock eingetreten ist, gibt es im Betriebsmittelgraph (Holt-Diagrams, Bsys1) einen Zyklus
- Für einen Deadlock müssen alle 4 Voraussetzungen erfüllt sein
- Nested Locks
- Zyklische Warteabhängigkeiten
- Gegenseitiger Ausschluss (Locks)
- Sperren ohne Timeout / Abbruch
Deadlocks vermeiden
- Ziel: Einer der vier Voraussetzungen verhindern
- Die geschachtelten Ressourcen nur in aufsteigender Reihenfolge reservieren (locken)
- Verhindert zyklische Warteabhängigkeiten
- Grobgranulare Locks wählen
- z.B. "ganze Bank sperren bei Kontozugriff"
- Verhindert geschachtelte Locks
- Ist aber evtl. sehr ineffizient
Starvation
Kontinuierliche Fortschrittsbehinderung von Threads wegen Fairness-Probleme - Es gibt kein Deadlock, die ressource wird immer frei, aber der wartende Thread wird ständig von anderen Threads überholt und die Bedingung wird nie erfüllt - Liveness Problem: Threads werden für unbestimmte Zeit aufgehalten, aber nicht unendlich lange - Java-Monitor ist Starvation-Anfällig, weil es keine Fairness gibt - Vermeidung: Fairness einbauen, z.B. bei Java Semaphoren. Länger wartende Threads haben Vortritt