go to start Ueb W2 revision: 1.24
|home |print view |recent changes |changed September 27, 2008 |
exact
|You are 107.20.129.212 <- set your identity!

79c79
Die Klasse KontoUebertragThread implementiert dazu die Methode kontoTransfer (siehe Testprogramm KontoUebertragTest.java). Das Testprogramm erzeugt mehrere Threads, die teilweise auf denselben Konto-Objekten operieren.
Die Klasse KontoUebertragThread implementiert dazu die Methode kontoTransfer (siehe Testprogramm [http:files/KontoUebertragTest.java KontoUebertragTest.java]). Das Testprogramm erzeugt mehrere Threads, die teilweise auf denselben Konto-Objekten operieren.
95a96,99
'''Vorlagen'''
* [http:files/KontoUebertragTest.java KontoUebertragTest.java]
* [http:files/Konto.java Konto.java]
101c105
Benutzen Sie die Vorgabe AmpelBetrieb.java.
Benutzen Sie die Vorgabe [http:files/Ampelbetrieb.java AmpelBetrieb.java].
119a124,126
'''Vorlagen'''
* [http:files/Ampelbetrieb.java AmpelBetrieb.java]
127c134
Vorgegeben ist eine Implementation eines zirkulären Buffers. IBuffer ist in der Datei IBuffer.java und CircularBuffer in der Datei CircularBuffer.java implementiert. Als erstes soll ein "Producer" und ein "Consumer" implementiert werden. Unten ist das Gerüst dieser abgebildet:
Vorgegeben ist eine Implementation eines zirkulären Buffers. IBuffer ist in der Datei [http:files/IBuffer.java IBuffer.java] und CircularBuffer in der Datei [http:files/CircularBuffer.java CircularBuffer.java] implementiert. Als erstes soll ein "Producer" und ein "Consumer" implementiert werden. Unten ist das Gerüst dieser abgebildet:
150c157
Für Producer und Consumer wurde bereits ein Testprogramm (class CircBufferTest) geschrieben:
Für Producer und Consumer wurde bereits ein Testprogramm [http:files/CircBufferTest.java CircBufferTest.java] geschrieben:
165,169c172,175
http:files/Konto.java
http:files/KontoUebertragTest.java
'''Vorlagen'''
* [http:files/CircBufferTest.java CircBufferTest.java]
* [http:files/CircularBuffer.java CircularBuffer.java]
* [http:files/IBuffer.java IBuffer.java]

Sections: ''Übungsserie 2: Einführung Synchronisation'' | '''Aufgabe 1: Verständnisfragen''' | '''Aufgabe 2: Konto-Übertrag''' | '''Aufgabe 3''' | '''Aufgabe 4: Producer-Consumer Problem''' |

Übungsserie 2: Einführung Synchronisation ^

Zielsetzung: Kennenlernen und Anwenden der Java-Synchronisationskonstrukte synchronized, wait, notify und notifyAll.


Aufgabe 1: Verständnisfragen ^

Beantworten Sie bitte diese Fragen, bevor Sie zu den praktischen Übungen übergehen.

  1. Was sind im täglichen Leben (ausser bei einem Bankkonto) Situationen, wo Race Conditions auftreten können (und von Software verhindert werden)?

  1. Welche Java-Operationen laufen atomar (unteilbar) ab?

  1. Auf den Vorlesungsfolien gibt es ein Beispiel von einem Bankkonto, das magisch von zwei parallelen Prozessen hochgezählt wird. Es werden zwei Synchronisations-Varianten gezeigt:

'Variante 1: in Klasse Konto:'

public synchronized void veraendereWert( int delta ) {
   this.wert += delta;
}
... und in Klasse KontoThread:
private void erhoeheKontostand() {
   myKonto.veraendereWert(1);
   totalInc = totalInc + 1;
}

'Variante 2: '

public void veraendereWert( int delta ) {
   this.wert += delta;
}
private void erhoeheKontostand() {
   synchronized( myKonto ) {
      myKonto.veraendereWert( 1 );
   }
   totalInc = totalInc + 1;
}
3a) Warum ist die Variante 2 weniger "sicher" als die Lösung 1?

3b) Welches ist das (versteckte) Lock-Objekt, das in Variante 1 gehalten wird?

  1. In welcher Klasse sind die Methoden 'notify' und 'notifyAll' zuhause? Warum nicht in der Klasse 'Thread'?


Aufgabe 2: Konto-Übertrag ^

Wir haben in der Vorlesung gesehen, dass die folgende Konto-Klasse "thread-safe" ist, da alle public-Methoden als "synchronized" implementiert sind.

class Konto { // als Monitor konzipiert

	private int id;
	private int kontoStand = 0;

	public Konto(int id, int initialBetrag) {
		this.id = id; this.kontoStand = initialBetrag;
	}

	public int getId() {
	   return id;
	}

	public synchronized int getKontoStand () {
	   return kontoStand;
	}

	public synchronized void setKontoStand(int betrag) {
		this.kontoStand=betrag;
	}

	public   synchronized void veraendereKontoStand (int delta) {
		this.kontoStand += delta;
	}
}
Ein SW-Entwickler der noch nicht viel von Synchronisation gehört hat, implementiert, aufbauend auf der Klasse Konto, eine Operation für den Transfer eines Geldbetrages zwischen zwei Konti. Die Klasse KontoUebertragThread implementiert dazu die Methode kontoTransfer (siehe Testprogramm KontoUebertragTest.java). Das Testprogramm erzeugt mehrere Threads, die teilweise auf denselben Konto-Objekten operieren.
class KontoUebertragThread extends Thread {
/*  Betrag vom Konto vonKonto zum Konto nach Konto uebertragen */
  	public void kontoTransfer() { 
  	   if (vonKonto.getKontoStand() > betrag) { //Konto darf nicht ueberzogen werden
	       	vonKonto.veraendereKontoStand(-betrag);
	       	nachKonto.veraendereKontoStand(betrag);
	       	totalInc += betrag;
	  }
  	}
}

  1. Diese Methode kontoTransfer ist nicht thread-safe! Wieso? Konstruieren Sie dazu mögliche verzahnte Abläufe der Threads, die zu inkonsistenten Resultaten führen.
  2. Erweitern Sie die Klasse Konto um eine sichere (thread-safe) Methode kontoTransfer. Sie können diese Methode als Klassen- und/oder Objekt-Methode implementieren. Passen Sie das Testprogramm entsprechend an. Bemerkung: Es macht Sinn, diese Methode in die Klasse Konto zu integrieren, da dann die Synchronisation gekapselt und nicht Aufgabe des Verwenders der Konto-Klasse ist.
  3. Ihre Lösung wird wahrscheinliche "recursive locks" enthalten, d.h. Sie werden in einem synchronized-Block einen weiteren synchronized-Block desselben Objektes aufrufen. Dies ist mit einer kleinen Einbusse an Effizienz verbunden. Welche Optimierung drängt sich da auf?
  4. Ändern Sie das Testprogramm so ab, dass ein Deadlock entsteht. Wie lassen sich Deadlocks generell vermeiden? Optional: Erweitern Sie ihre Implementation der kontoTransfer-Methode, so dass Deadlocks nicht auftreten können.


Aufgabe 3 ^

In dieser Aufgabe sollen Sie die Funktionsweise einer Ampel und deren Nutzung nachahmen.

  1. Schreiben Sie zunächst eine Klasse Ampel mit zwei Methoden zum Setzen der Ampel auf rot bzw. grün (die Zwischenphase gelb spielt keine Rolle; Sie können von diesem Zustand abstrahieren)! Eine dritte Methode mit dem Namen passieren soll das Vorbeifahren eines Fahrzeugs an dieser Ampel nachbilden: Ist die Ampel rot, so wird der aufrufende Thread angehalten, und zwar solange, bis die Ampel grün wird. Ist die Ampel dagegen grün, so kann der Thread sofort aus der Methode zurückkehren, ohne den Zustand der Ampel zu verändern. Verwenden Sie wait, notify und notifyAll nur an den unbedingt nötigen Stellen!

Benutzen Sie die Vorgabe AmpelBetrieb.java.

  1. Erweitern Sie nun die Klasse Auto (abgeleitet aus Thread). Im Konstruktor wird eine Referenz auf ein Feld von Ampeln übergeben, wobei diese Referenz in einem entsprechenden Attribut der Klasse Auto gespeichert wird. In der run-Methode werden alle Ampeln dieses Feldes passiert, und zwar in einer Endlosschleife (d.h. nach dem Passieren der letzten Ampel des Feldes wird wieder die erste Ampel im Feld passiert).

Natürlich darf das Auto erst dann eine Ampel passieren, wenn diese auf grün ist! Für die Simulation der Zeitspanne fürs Passieren können Sie die folgende Anweisung verwenden:

sleep((int)(Math.random() * 500));

  1. Beantworten Sie genau eine der folgenden Fragen:

a) Falls Sie bei der Implementierung der Klasse Ampel die Methode notifyAll benutzt haben: Hätten Sie statt notifyAll auch die Methode notify verwenden können oder haben Sie notifyAll unbedingt gebraucht? Begründen Sie Ihre Antwort!

b) Falls Sie bei der Implementierung der Klasse Ampel die Methode notify benutzt haben: Begründen Sie, warum Sie notifyAll nicht unbedingt gebraucht haben!

  1. Testen Sie das Programm AmpelBetrieb.java. Die vorgegebene Klasse AmpelBetrieb implementiert eine primitive Simulation von Autos, welche die Ampeln passieren. Studieren Sie den Code dieser Klasse und überprüfen Sie, ob die erzeugte Ausgabe sinnvoll ist.

'Bemerkung zur Vorgabe: Der Einfachheit halber sind alle Klassen in einer Datei zusammengefasst. Natürlich steht es ihnen frei, die Klassen auf mehrere Dateien aufzuteilen.'


Aufgabe 4: Producer-Consumer Problem ^

Zielsetzung: Umgang mit den Synchronisationsmöglichkeiten von Java Mutual Exclusion und Condition Synchronisation.

1. Producer- und Consumer-Thread Vorgegeben ist eine Implementation eines zirkulären Buffers. IBuffer ist in der Datei IBuffer.java und CircularBuffer in der Datei CircularBuffer.java implementiert. Als erstes soll ein "Producer" und ein "Consumer" implementiert werden. Unten ist das Gerüst dieser abgebildet:

class Producer extends Thread {
    public Producer(String name, IBuffer buffer, int prodTime) {
		...
    }
    public void run() {
		...
    }
}

class Consumer extends Thread
{
    public Consumer(String name, IBuffer buffer, int consTime) {
	 	...
    }
    public void run() {
		...
    }
}
Der Producer soll Daten in den Buffer einfüllen und der Consumer soll Daten herausholen. Auf den Buffer soll nur über das Interface zugegriffen werden. Das Zeitintervall, in dem Producer Daten einfüllen kann mit sleep((int)(Math.random() * prodTime)). Die Zeit fürs konsumieren können Sie entsprechend mit sleep((int)(Math.random() * consTime)) bestimmen.

 

Für Producer und Consumer wurde bereits ein Testprogramm (class CircBufferTest) geschrieben:

Testen Sie nun damit ihre Consumer-, und Producer-Klassen. Versuchen sie den generierten Output auf der Console richtig zu interpretieren! Spielen sie mit den Zeitintervallbereichen von Producer (maxProdTime) und Consumer (maxConsTime) und ziehen sie Schlüsse. Erstellen sie über die Modifikation von prodCount und consCount mehrere Producer bzw Consumer. Tip: Generieren sie in den selber implementierten Klassen keine eigene Ausgabe. Ändern sie den bestehenden Code nicht.

  1. In der vorangehenden Übung griffen mehrere Threads auf den gleichen Buffer zu. Der Buffer ist aber nicht thread-safe. Was wir gemacht haben, ist daher nicht tragbar. Deshalb soll jetzt eine Wrapper Klasse geschrieben werden, welche die CircularBuffer-Klasse "thread-safe" macht. Aufrufe von put blockieren, solange der Puffer voll ist; d.h. bis also mind. ein leeres Puffer-Element vorhanden ist. Analog dazu blockieren Aufrufe von get solange der Puffer leer ist, d.h bis also mind. ein Element im Puffer vorhanden ist.

Tipp: Verwenden Sie den Java Monitor des GuardedCircularBuffer-Objektes! Wenn die Klasse fertig implementiert ist, soll sie in der "CircBufferTest" Klasse verwendet werden.

Beantworten Sie genau eine der folgenden Fragen:

a) Falls Sie bei der Implementierung der Klasse GuardedCircularBuffer die Methode notifyAll benutzt haben: Hätten Sie statt notifyAll auch die Methode notify verwenden können oder haben Sie notifyAll unbedingt gebraucht? Begründen Sie Ihre Antwort!

b) Falls Sie bei der Implementierung der Klasse GuardedCircularBuffer die Methode notify benutzt haben: Begründen Sie, warum Sie notifyAll nicht unbedingt gebraucht haben!

http:files/Konto.java

http:files/KontoUebertragTest.java


|home |print view |recent changes |changed September 27, 2008 |
exact
|You are 107.20.129.212 <- set your identity!

Ueb W2 revision: 1.24
go to start