„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
class MyClass
{
private String name1 = "Ally";
private String name2 = "Lena";

public void swap()
{
synchronized (this)
{
String s = name1;
name1 = name2;
name2 = s;
}
}
}
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
String s1 = name1; //Ally
name1 = name2; //Lena
name2 = s1; //Ally

String s2 = name1; //Lena
name1 = name2; //Ally
name2 = s2; //Lena
String s1 = name1;
name1 = name2;
//anderer Thread wird ausgeführt
name2 = s1;
//der Thread wartet, bis das Mutex entsperrt ist

String s2 = name1;
name1 = name2;
//anderer Thread wird ausgeführt
//anderer Thread wird ausgeführt
name2 = s2;
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
class MyClass
{
private String name1 = "Ally";
private String name2 = "Lena";

public void swap()
{
synchronized (this)
{
String s = name1;
name1 = name2;
name2 = s;
}
}

public void swap2()
{
synchronized (this)
{
String s = name1;
name1 = name2;
name2 = s;
}
}
}
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
class MyClass
{
private static String name1 = "Ally";
private static String name2 = "Lena";

public synchronized void swap()
{
String s = name1;
name1 = name2;
name2 = s;
}

public static synchronized void swap2()
{
String s = name1;
name1 = name2;
name2 = s;
}
}
class MyClass
{
private static String name1 = "Ally";
private static String name2 = "Lena";

public void swap()
{
synchronized (this)
{
String s = name1;
name1 = name2;
name2 = s;
}
}

public static void swap2()
{
synchronized (MyClass.class)
{
String s = name1;
name1 = name2;
name2 = s;
}
}