"Grundkurs Programmieren in Java - (6. Auflage, 2011)"
2001-2011, Carl Hanser Verlag

Lösungsvorschlag zu Aufgabe 18.1 (Version 2.0)

(c) 2003-2011 D. Ratz, J. Scheffler, D. Seese, J. Wiesenberger

Das Programmverhalten kann überhaupt nicht vorhergesagt werden, da es stark von der zeitlichen Verzahnung der KartenTerminal-Threads abhängt. Eine typische Ausgabe wäre zum Beispiel
  Karten-Terminal 1: Sitzplatz 1 verkauft
  Karten-Terminal 2: Sitzplatz 1 verkauft
  Karten-Terminal 1: Sitzplatz 2 verkauft
  Karten-Terminal 2: Sitzplatz 2 verkauft
  Karten-Terminal 1: Sitzplatz 3 verkauft
  Karten-Terminal 2: Sitzplatz 3 verkauft
  Karten-Terminal 1: Sitzplatz 4 verkauft
  ...

Dies erklärt sich dadurch, daß t1 und t2 regelmäszlig;ig deaktiviert und wieder aktiv werden. In der nachfolgenden Tabelle wird eine mögliche Abarbeitung der Anweisungen der beiden Threads beschrieben. Zu beachten ist dabei, daß es sich bei der Variable sitzPlatz jeweils um dieselbe Instanzvariable des Objekts KonzertDaten handelt, auf die beide Threads zugreifen. Andererseits hat jeder Thread seine eigene lokale Variable n.

Die simultane Abarbeitung der Anweisungen der beiden Threads t1 und t2 kann man z.B. wie folgt beschreiben:

t1 t2 daten.sitzPlatz
daten.freierPlatz() nicht aktiv 0
int n = sitzPlatz (=0) nicht aktiv 0
nicht aktiv daten.freierPlatz() 0
nicht aktiv int n = sitzPlatz (=0) 0
return sitzPlatz = n + 1; (=1)nicht aktiv 1
daten.freierPlatz() nicht aktiv 1
int n = sitzPlatz (=1) nicht aktiv 1
nicht aktiv return sitzPlatz = n + 1; (=1) 1
nicht aktiv daten.freierPlatz() 1
nicht aktiv int n = sitzPlatz (=1) 1
return sitzPlatz = n + 1; (=2)nicht aktiv 2
... ... ...

Wenn man sich die Anweisungsfolge von Thread t2 anschaut, sieht man, daß zuerst der korrekte sitzPlatz-Wert 0 in der lokalen Variablen n gespeichert wird. Der Thread wird dann inaktiv. Während dieser Zeit wird sitzPlatz vom Thread t1 inkrementiert. Der Thread t2 wird wieder aktiv und speichert n+1, also 1, in sitzPlatz. Aufgrund der Veränderung durch t1 ist dieser Wert nun nicht mehr korrekt.

Das unvorhersehbare Verhalten des Programmes kann man beseitigen, indem man dafür sorgt, daß immer nur ein Thread die Methode freierPlatz für das Objekt daten aufrufen darf und alle anderen Threads während der Ausführung dieses einen Aufrufs keine weiteren freierPlatz-Aufrufe absetzen dürfen.

Dies erreicht man dadurch, daß man die Methode freierPlatz durch

  synchronized int freierPlatz() {
  ...
zu einer synchronized-Methode macht. Jedes Objekt besitzt eine Sperre, die Threads erwerben müssen, bevor sie eine synchronized-Methode ausführen können. Wenn eine synchronized-Methode für ein Objekt aufgerufen wird, wird die Sperre untersucht, um festzustellen, ob ein anderer Thread gerade eine synchronized-Methode für dieses Objekt ausführt. Ist dies nicht der Fall, dann kann der aktuelle Thread die Sperre erwerben und mit der Ausführung der Methode beginnen. Wenn bereits ein anderer Thread im Besitz der Sperre ist, wird der aktuelle Thread so lange blockiert, bis die Sperre wieder freigegeben wurde. Das Objekt selbst führt eine Warteliste mit den blockierten Threads. Wenn ein Thread die Ausführung einer synchronized-Methode beendet hat, gibt er die Sperre wieder frei.