„Hallo Amigo! Wir haben ein Allheilmittel – ein Heilmittel für alle Krankheiten. Wie wir bereits gesehen haben, ist unkontrolliertes Thread-Umschalten ein Problem.“
„Warum können die Threads nicht selbst entscheiden, wann sie zum nächsten Thread wechseln? Sie tun alles, was sie sollen, und signalisieren dann: ‚Ich bin fertig!‘?“
„Wenn die Threads selbst das Umschalten steuern würden, wäre das Problem noch größer. Angenommen, du hast einen schlecht geschriebenen Code und der Thread gibt die CPU nie ab. Früher hat das so funktioniert. Und das war ein echter Albtraum.“
„Okay. Was ist denn dann die Lösung?“
„Andere Threads sperren. So wird‘s gemacht.“
Es ist deutlich geworden, dass sich Threads gegenseitig stören, wenn sie versuchen, gemeinsame Objekte und/oder Ressourcen zu verwenden. So wie wir es im Beispiel mit der Konsolenausgabe gesehen haben: Wir haben eine Konsole und alle Threads geben etwas darauf aus. Das ist ein ziemliches Chaos.
Also wurde ein besonderes Objekt erfunden: das Mutex. Es ist wie ein Schild an einer Toilettentür, auf dem ‚frei/besetzt‘ steht. Es hat zwei Zustände: das Objekt ist frei oder besetzt. Diese Zustände werden auch als ‚gesperrt‘ (locked) und ‚entsperrt‘ (unlocked) bezeichnet.
Wenn ein Thread ein mit anderen Threads gemeinsam genutztes Objekt benötigt, überprüft er das dem Objekt zugeordnete Mutex. Wenn das Mutex entsperrt ist, sperrt der Thread es (markiert es als ‚besetzt‘) und beginnt dann, die gemeinsame Ressource zu verwenden. Nachdem der Thread seine Arbeit getan hat, wird das Mutex entsperrt (als ‚frei‘ markiert).
Wenn der Thread das Objekt verwenden will und das Mutex gesperrt ist, dann schläft der Thread, während er wartet. Wenn das Mutex durch den besetzenden Thread schließlich entsperrt wird, wird unser Thread es sofort sperren und ausgeführt werden. Die Analogie zu einem Toilettenschild ist perfekt.
„Wie arbeite ich denn mit einem Mutex? Muss ich spezielle Objekte erstellen?“
„Es ist noch viel einfacher. Javas Entwickler haben dieses Mutex in die Object-Klasse integriert. Du musst es also gar nicht selbst erstellen. Es ist bereits ein Teil jedes Objekts. Und so funktioniert das Ganze:“
Code | Beschreibung |
---|---|
|
Die swap-Methode vertauscht die Werte der Variablen name1 und name2.
Was könnte passieren, wenn sie von zwei Threads gleichzeitig aufgerufen wird? |
Tatsächliche Code-Ausführung | Code des ersten Threads | Code des zweiten Threads |
---|---|---|
|
|
|
Das Ergebnis |
---|
Die Werte der Variablen wurden zweimal vertauscht und kehrten so an ihren ursprünglichen Platz zurück. |
Achte auf das Schlüsselwort synchronized.
„Ja, was heißt das?“
„Wenn ein Thread in einen als synchronized markierten Codeblock eintritt, sperrt die Java-Maschine sofort das Mutex des in Klammern nach dem Wort synchronized angegebenen Objekts. Kein anderer Thread kann in diesen Block eintreten, bis unser Thread ihn verlässt. Sobald unser Thread den als synchronized markierten Block verlässt, wird das Mutex sofort und automatisch entsperrt und steht für die Übernahme durch einen anderen Thread zur Verfügung.“
Wenn das Mutex besetzt ist, dann wartet unser Thread, dass es wieder frei wird.
„So einfach und so elegant. Das ist eine sehr schöne Lösung.“
„Ja. Aber was glaubst du, was in diesem Fall passieren wird?“
Code | Beschreibung |
---|---|
|
Die Methoden swap und swap2 teilen sich das gleiche Mutex (das this-Objekt). |
Was passiert, wenn ein Thread die swap-Methode und ein anderer Thread die swap2-Methode aufruft?
„Da das Mutex das gleiche ist, muss der zweite Thread warten, bis der erste Thread den synchronized-Block verlässt. Es wird also überhaupt keine Probleme mit dem gleichzeitigen Zugriff geben.“
„Sehr gut, Amigo! Das ist die richtige Antwort!“
Ich möchte noch darauf hinweisen, dass mit synchronized nicht nur Codeblöcke, sondern auch Methoden markiert werden können. Das bedeutet Folgendes:
Code | Das passiert tatsächlich |
---|---|
|
|
GO TO FULL VERSION