Informatik

Monitor in der Informatik: Synchronisation verstehen

Monitor in der Informatik: Synchronisation verstehen
Monitor in der Informatik: Synchronisation verstehen
Für Quiz, Lückentext, Lernkarten und Fortschritt ist JavaScript nötig. Alle Inhalte und Lösungen bleiben direkt lesbar.

Hier geht es um den Monitor als Synchronisationskonzept, nicht um einen Bildschirm oder das Bildungsmonitoring. Ein Monitor bündelt gemeinsam genutzte Daten mit den Operationen darauf. Er sorgt dafür, dass kritische Zugriffe desselben Monitors nur von einem Thread zur Zeit ausgeführt werden. Bedingungsvariablen koordinieren zusätzlich das Warten auf einen passenden Zustand.

Deine Lernziele

Hake ab, was du schon kannst — und komm am Ende hierher zurück!

Warum ein Monitor nötig ist

Stell dir zwei Threads vor, die auf dieselbe Datenstruktur zugreifen. Werden ihre Einzelschritte ungünstig verschachtelt, kann ein Thread mit einem veralteten Zwischenstand weiterarbeiten. Das Ergebnis kann dadurch widersprüchlich werden. Eine solche vom zeitlichen Ablauf abhängige Fehlersituation heißt Race Condition.

Definition

Monitor

Ein Monitor ist eine geschlossene Einheit aus gemeinsam genutzten Daten und den Prozeduren oder Methoden, die auf diese Daten zugreifen. Er steuert den Zutritt zu kritischen Operationen und kann Threads zusätzlich auf Bedingungen warten lassen.

Der Monitor hebt die Synchronisation auf ein höheres Abstraktionsniveau: Der Programmcode nutzt die vorgesehenen Monitor-Operationen. Übersetzung oder Laufzeitsystem realisieren die nötigen Synchronisationsprimitive. Damit liegt der Schutz direkt bei den Daten und ihren zulässigen Zugriffen.

Beispiel

Ein Monitor verwaltet einen gemeinsamen Puffer. ablegen() verändert den Puffer, entnehmen() ebenfalls. Beide Operationen gehören zum selben Monitor. Führt Thread A gerade ablegen() aus, muss Thread B vor entnehmen() warten, bis A den Monitor verlassen hat. So verändern beide den Puffer nicht gleichzeitig.

Teste dich
Frage 1 von 1LeichtWelche Beschreibung trifft einen Monitor am besten?
Lösung: Er bündelt gemeinsame Daten und synchronisierte Zugriffsoperationen in einer Einheit. — Ein Monitor kapselt gemeinsame Daten und die Operationen darauf. Sein Zutrittsmechanismus schützt kritische Zugriffe.
Wechselseitiger Ausschluss schützt kritische Abschnitte

Ein kritischer Abschnitt ist ein Teil einer Operation, der gemeinsam genutzte Daten so liest oder verändert, dass eine unkontrollierte Verschachtelung zu Fehlern führen könnte. Monitor-Operationen mit solchen Abschnitten werden unter wechselseitigem Ausschluss ausgeführt.

Merke

Bei demselben Monitor befindet sich höchstens ein Thread zur Zeit in einer geschützten Monitor-Operation. Ein weiterer Thread wird blockiert, bis der Monitor frei ist.

Das ähnelt einem Raum mit nur einem Platz: Befindet sich Thread A im Raum, wartet Thread B davor. Verlässt A den Raum, darf ein wartender Thread eintreten. Entscheidend ist, dass sich diese Regel auf denselben Monitor bezieht. Zwei verschiedene Monitorobjekte besitzen getrennte Zutrittskontrollen.

Nicht jede Prozedur eines Moduls muss einen kritischen Abschnitt enthalten. Der Monitor kann auch Zugriffsprozeduren anbieten, die keinen exklusiven Schutz benötigen. Geschützt werden muss genau der Teil, dessen unkontrollierte Ausführung den gemeinsamen Zustand gefährdet.

Beispiel

Thread A ruft eintragen() auf und betritt den Monitor. Während A den gemeinsamen Zustand ändert, ruft Thread B loeschen() am selben Monitor auf. B wird blockiert. Erst nachdem A die Operation beendet und den Monitor freigegeben hat, kann B eintreten. Die Änderungen laufen dadurch nacheinander ab.

Teste dich
Frage 1 von 1MittelThread A befindet sich in einer geschützten Operation des Monitors M. Thread B ruft eine andere geschützte Operation desselben Monitors M auf. Was geschieht?
Lösung: B wird blockiert, bis M freigegeben wird. — Der Monitor erlaubt nur einen aktiven Thread in seinen geschützten Operationen. Der zweite Aufruf muss warten.
Mit Bedingungen sinnvoll warten

Exklusiver Zutritt löst noch nicht jedes Problem. Ein Thread kann den Monitor betreten und feststellen, dass der gemeinsame Zustand eine Fortsetzung nicht erlaubt. Beim Puffer wäre entnehmen() zum Beispiel erst sinnvoll, wenn ein Element vorhanden ist.

Ständiges Verlassen, erneutes Eintreten und Prüfen wäre unkontrolliertes Warten. Monitore verwenden deshalb Bedingungsvariablen. Jede Bedingungsvariable steht für einen Wartegrund und besitzt eine eigene Warteschlange.

Definition

Bedingungsvariable

Eine Bedingungsvariable koordiniert Threads, die auf einen bestimmten Zustand der Monitordaten warten. Ob der benötigte Zustand tatsächlich vorliegt, stellt der Thread durch Prüfen der Daten fest.

Die beiden grundlegenden Operationen sind:

  1. wait(): Der aktive Thread wird blockiert, gibt den Monitor frei und wird in die Warteschlange dieser Bedingungsvariablen eingereiht. Nun kann ein anderer Thread den Monitor betreten und den Zustand verändern.
  2. signal(): Ein Thread signalisiert die Bedingungsvariable. Ein dort wartender Thread wird deblockiert. Wartet niemand, hat das Signal keine Wirkung.
Beispiel

Ein Verbraucher betritt den Puffermonitor und erkennt: Der Puffer ist leer. Er ruft wait() auf der Bedingung nichtLeer auf. Dadurch gibt er den Monitor frei. Ein Erzeuger kann nun eintreten und ein Element ablegen. Danach ruft er signal() für nichtLeer auf. Der wartende Verbraucher wird zur Fortsetzung zugelassen. Wann genau er weiterläuft, hängt von der Monitorsemantik ab.

Teste dich
Frage 1 von 2MittelWarum gibt wait() den Monitor frei?
Lösung: Damit ein anderer Thread eintreten und den erwarteten Zustand herstellen kann. — Würde der wartende Thread den Monitor behalten, könnte kein anderer Thread die Monitordaten ändern und die Bedingung herstellen.
Frage 2 von 2MittelWas bewirkt signal(), wenn an der betreffenden Bedingungsvariablen niemand wartet?
Lösung: Der Aufruf bleibt ohne Wirkung. — signal() deblockiert einen bereits wartenden Thread. Ohne Wartenden gibt es niemanden zu deblockieren.
Hoare- und Mesa-Semantik unterscheiden

Nach signal() gibt es einen Konflikt: Der signalisierende Thread ist noch im Monitor, zugleich könnte der deblockierte Thread fortfahren. Da nur einer aktiv sein darf, legt die Monitorsemantik die Reihenfolge fest.

SemantikReaktion auf signal()Bedeutung für den Wartenden
Hoare-Typ, „Signal and Wait“Der signalisierende Thread wird blockiert.Ein wartender Thread übernimmt den Monitor sofort und setzt bei wait() fort.
Mesa-Typ, „Signal and Continue“Der signalisierende Thread arbeitet im Monitor weiter.Ein Wartender wechselt zunächst in die allgemeine Monitor-Warteschlange und tritt später ein.

Beim Hoare-Typ gilt der signalisierte Zustand beim unmittelbaren Fortsetzen des Wartenden. Beim Mesa-Typ kann sich der Zustand bis zu dessen späterem Eintritt erneut ändern. Deshalb muss der Wartende die benötigte Bedingung dann erneut prüfen.

Beispiel

Der Puffer enthält nach dem Ablegen genau ein Element. Der Erzeuger signalisiert nichtLeer.

  • Hoare-Typ: Ein wartender Verbraucher übernimmt den Monitor sofort. Der Erzeuger wartet.
  • Mesa-Typ: Der Erzeuger darf zunächst weiterarbeiten und den Monitor später verlassen. Der Verbraucher konkurriert anschließend um den Zutritt und prüft erneut, ob der Puffer noch nicht leer ist.
Teste dich
Frage 1 von 2MittelEin signalisierender Thread arbeitet nach signal() zunächst im Monitor weiter. Welche Semantik liegt vor?
Lösung: Mesa-Typ, „Signal and Continue“ — „Continue“ bezeichnet das Weiterarbeiten des signalisierenden Threads. Der deblockierte Thread erhält den Monitor erst später.
Frage 2 von 2SchwerWarum prüft ein unter Mesa-Semantik deblockierter Thread seine Bedingung erneut?
Lösung: Weil sich der gemeinsame Zustand vor seinem späteren Monitoreintritt wieder geändert haben kann. — Unter Mesa-Semantik ist das Signal keine Garantie, dass der Zustand beim späteren Eintritt noch passt. Maßgeblich sind die aktuellen Monitordaten.
Das Monitorprinzip in Java anwenden

In Java besitzt grundsätzlich jedes Objekt Monitorfähigkeiten. Eine mit synchronized gekennzeichnete Instanzmethode nutzt den Monitor des betreffenden Objekts. Rufen mehrere Threads synchronized-Methoden desselben Objekts auf, kann sich höchstens einer von ihnen zur Zeit in einer solchen Methode dieses Objekts befinden.

Beispiel

Ein Objekt puffer bietet die synchronisierten Methoden ablegen() und entnehmen() an. Thread A führt gerade puffer.ablegen() aus. Ruft Thread B gleichzeitig puffer.entnehmen() auf, wartet B am Monitor von puffer. Ein Aufruf an einem anderen Pufferobjekt hätte dagegen einen anderen Objektmonitor.

Java stellt dafür Methoden von Object bereit:

  • wait() blockiert den aufrufenden Thread und gibt den Monitor des Objekts frei.
  • notify() deblockiert einen beliebigen Thread, der an diesem Monitor wartet. Dieser läuft erst weiter, wenn der Monitor frei ist; die Auswahl muss nicht fair sein.
  • notifyAll() deblockiert alle dort wartenden Threads. Danach konkurrieren sie um den freien Monitor.

Ein deblockierter Thread prüft seine erwartete Bedingung erneut. Sie kann noch nicht gelten oder ein schnellerer Thread kann den passenden Zustand bereits wieder verändert haben.

Merke

notify() bedeutet nicht „Der Wartende läuft sofort“. Es bedeutet: Ein Wartender darf wieder um den Objektmonitor konkurrieren.

Teste dich
Frage 1 von 2LeichtWelche Java-Methode gibt beim Warten den Objektmonitor frei?
Lösung: wait()wait() blockiert den aufrufenden Thread und gibt den Objektmonitor frei, damit ein anderer Thread eintreten kann.
Frage 2 von 2MittelWas unterscheidet notifyAll() von notify()?
Lösung: notifyAll() deblockiert alle Wartenden; notify() nur einen beliebigen Wartenden. — Das Deblockieren hebt den exklusiven Monitorzutritt nicht auf. Es macht Threads nur wieder zu Bewerbern um den Monitor.
Karteikasten
Karteikasten

Überlege zuerst selbst und drehe die Karte anschließend zum Prüfen um.

Alles auf einen Blick
Mindmap
  • Monitor
    • schützt gemeinsame Daten
      • kritische Abschnitte
      • wechselseitiger Ausschluss
    • koordiniert Bedingungen
      • `wait()` gibt den Monitor frei
      • `signal()` deblockiert einen Wartenden
    • bestimmt die Fortsetzung
      • Hoare: Wartender sofort
      • Mesa: Signalisierender zuerst
    • erscheint in Java
      • `synchronized` schützt Objektzugriffe
      • `notify()` und `notifyAll()` wecken Wartende
Abschluss-Check
Teste dich
Frage 1 von 4LeichtWas ist die zentrale Schutzwirkung eines Monitors?
Lösung: Geschützte Operationen desselben Monitors werden wechselseitig ausgeschlossen. — Der Monitor ordnet den Zutritt zu kritischen Operationen und verhindert damit deren gleichzeitige Ausführung am selben Monitor.
Frage 2 von 4MittelEin Thread wartet im Monitor auf einen nicht leeren Puffer. Welche Handlung erlaubt einem Erzeuger, den Pufferzustand zu ändern?
Lösung: Der wartende Thread ruft wait() auf und gibt dabei den Monitor frei. — wait() verbindet Blockieren und Freigeben. Dadurch kann ein anderer Thread den benötigten Zustand herstellen.
Frage 3 von 4MittelEin Java-Thread wurde durch notify() deblockiert. Welche Folgerung ist sicher?
Lösung: Er kann um den Monitor konkurrieren und muss die erwartete Bedingung erneut prüfen. — Das Deblockieren garantiert weder sofortigen Zutritt noch einen unveränderten Zustand. Deshalb folgt eine erneute Prüfung.
Frage 4 von 4SchwerEin System soll garantieren, dass nach signal() ein Wartender den Monitor sofort übernimmt. Welche Semantik passt?
Lösung: Hoare-Typ, „Signal and Wait“ — Beim Hoare-Typ wird der Signalisierende blockiert und ein Wartender setzt unmittelbar am wait() fort.

Passend dazu