PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Java - Frage zu wait() und notify()


mittelding
2012-07-02, 20:15:04
Hallo!

Um die Threadsynchronisation in Java mit wait und notify zu beschreiben, werden ja gerne Consumer-Producer Beispiele hergenommen. Der Producer möchte dauern eine Nachricht im System ablegen, der Consumer die Nachricht lesen. Die Beispiele sind dann so gestaltet, dass der Producer keine neue Nachricht ablegen können soll, wenn der Consumer die letzte Nachricht noch nicht gelesen hat - analog dazu soll der Consumer keine Nachricht lesen, wenn es keine neue Nachricht gbt seit dem letzten Lesevorgang.

Ich habe dazu folgendes codebeispiel gefunden:



public synchronized void put(int data) {
if (!empty)
try {
wait();
}
catch(InterruptedException e) {}
System.out.println("Eingetragen: "+data);
buffer = data;
empty = false;
notify();
}

public synchronized int get() {
if (empty)
try {
wait();
}
catch(InterruptedException e) {}
System.out.println("Abgeholt: "+buffer);
empty = true;
notify();
return buffer;
}


Um gleich zum Thema zu kommen: habe ich die Thematik einfach noch nicht verstanden, oder ist das gezeigte Beispiel nicht wirklich durchdacht?

Was ich als Problem sehe:

Angenommen, dort im Programm wird mit put etwas abgelegt, empty wäre also false. Jetzt kommen noch 10 andere Threads, welche ebenfalls mittels put etwas ablegen möchten - diese werden folglich alle Opfer von wait und somit schlafen gelegt, denn es wurde ja in der Zwischenzeit noch nichts ausgelesen.

Also, Zwischenstand: die Ablage ist voll, 10 andere Threads können deswegen nichts reinlegen und befinden sich im Wait-Zustand.

Jetzt kommt ein Thread daher, er möchte endlich mal was auslesen. Gesagt, getan, die Ablage ist nun frei für neuen Input, dann wird noch kurz notify betätigt und somit einer der 10 oben genannten Threads aufgeweckt, der dann etwas ablegen kann. Da der aufgeweckte Thread beim Ablegen erfolgreich war, ruft er danach noch notify auf - das wäre ursprünglich eigentlich dazu gedacht gewesen, einen Auslese-thread aufzuwecken, der schlafen gelegt wurde, weil es nichts zum Auslesen gegeben hätte. Jetzt gibt es in der schlafenden Runde aber garkeinen Lese-Thread, sondern nur Schreib-Threads. Und notify ist egal, wen oder was es aufweckt, hauptsache der Schläfer hängt am selben sperrobjekt.

Würde das jetzt nicht dazu führen, dass nun ein weiterer der 10 Schreib-Threads aufgeweckt wird, welcher nun etwas ablegen möchte, obwohl die Ablage voll ist? Ja, das ist meine Frage, ziemlich viel Text dafür, sorry :(

Falls ich recht habe - wie könnte man das Problem lösen? könnte man nicht einfach das "if(empty)" bzw. "if(!empty)" ersetzen durch "while(empty)" respektive "while(!empty)"? Wenn dann ein Schreibthread einen anderen Schreibthread aufwecken würde, würde der sich sofort wieder schlafen legen.


vielen Dank

Senior Sanchez
2012-07-02, 20:37:12
Na du vergisst noch etwas Wichtiges.

Die Methoden sind synchronized. Das bedeutet, dass nur ein Thread in jeder Methode gleichzeitig sein kann. Die Threads werden also nicht durch das wait blockiert, sondern kommen gar nicht erst in die Methode rein. In deinem Beispiel hat das zur Folge, dass durch das notify in der put-Methode keiner der anderen 9 Threads, die put ausführen wollen, aufgeweckt wird. Stattdessen kann der nächste die put-Methode betreten, die anderen werden durch das synchronized weiter blockiert. Das Flag empty ist auf false, er geht somit in den then-Block der if-Anweisung und wird erst jetzt durch wait() schlafen gelegt.

Erst wenn jetzt ein Verbraucherthread kommt und das notify aufruft, wacht der put-Thread wieder auf und kann etwas reinpacken.

Das Beispiel ist also korrekt.

Diese while()-Geschichten solltest du dir übrigens bei Threads nicht aneignen. Das ist busy-waiting und sorgt für schön hohe CPU-Last.

#44
2012-07-02, 20:41:37
Die beiden Methoden sind synchronisiert.
Solange ein Thread bei der Methodenausführung ist, kann kein 2. da hinein.

€: Zu spät...

Aber sleep-while Schleifen sind sicher kein busy-waiting...

Senior Sanchez
2012-07-02, 20:47:34
Aber sleep-while Schleifen sind sicher kein busy-waiting...

Ich habe das so interpretiert, dass er den ganzen if-Block und damit auch das wait durch ne Schleife ersetzen will.

creave
2012-07-02, 22:07:07
Also entweder stehe ich ähnlich auf dem Schlauch, oder ich kann mich meinen Vorrednern nicht anschließen. Ich denke, dass der TS recht hat.

Eure Argumentation mit dem synchronized stimmt so nicht. Es gibt ja nur einen einzigen Monitor, der verteilt wird, und das ist der des "Ablage"-Objekts selbst, auch welchem die ganze Zeit gearbeitet wird.
public synchronized void MethodName() { // hier geht die action } ist ja de facto identisch zu public void MethodName() { synchronized(this) { // hier geht die action } }.


Die Methoden sind synchronized. Das bedeutet, dass nur ein Thread in jeder Methode gleichzeitig sein kann.

Es bedeutet, dass überhaupt nur ein Thread in einer (nicht in jeder) Methode sein kann. Zu Zeitpunkt X läuft nur ein Thread über den Code, auch wenn ein anderer Thread die jeweils andere Methode aufrufen hätte wollen, der andere Thread darf also nicht mal in die andere Methode rein.

Die beiden Methoden sind synchronisiert.
Solange ein Thread bei der Methodenausführung ist, kann kein 2. da hinein.

Richtig, aber bei wait wird der Monitor aufgehoben. Wäre das nicht so, dann könnte auch in die andere Methode kein Thread mehr rein, da beide synchronized-Methoden den selben Monitor nutzen.

Wenn jetzt also ein Producer-Thread die Ablage betritt und herausfindet, dass die schon belegt ist, dann trifft er auf wait(), legt sich pennen und WICHTIG, gibt den Monitor frei.

Da der Monitor jetzt frei ist, kann der nächste Thread ankommen, sich den Monitor krallen und völlig nach belieben entweder die put- ODER die get-Methode betreten. Da ändert auch das synchronized nichts dran, der Monitor gehört nun dem zweiten Thread, und dem Monitor ist egal, in welche Methode Thread2 reingeht. Beide Methoden werden über den selben Monitor synchronisiert.

Und wenn in der wait-Schlafhalle nun 2 Producer darauf warten, bis die blöde Ablage endlich frei wird, dann ist es meiner Ansicht nach also wirklich so, dass folgendes passiert:

1. Consumer leert Ablage, ruft notify auf
2. Produzent1 erwacht, schreibt was in die Ablage, ruft notify auf
3. Produzent2 erwacht durch das notify von Produzent1 und überschreibt fälschlicherweise die Ablage, ohne dass sie zuvor gelesen worden wäre.


Also wenn das nicht stimmt, dann ist das gerade eine sehr wertvolle Erfahrung für mich :freak:
Kann sein ich liege daneben, dann bitte schnell korrigieren.

edit: in "Java ist auch eine Insel" ist auch so ein Consumer-Producer Beispiel drin, dort wird tatsächlich sowas gemacht: while ( nachrichten.size() == MAXQUEUE ) wait();
Keine Ahnung, ob das jetzt schön ist oder nicht, aber das könnte funktionieren. Das Problem beim TS ist ja, dass die Threads nach dem Aufwachen gar nicht erneut nachschauen, ob sie lesen oder schreiben dürfen, da sie an der Abfrage ja schon bereits vor dem Schlafen gehen vorbeigeschrammt sind. Das while hingegen erzwingt eine erneute Prüfung, so verstehe zumindest ich das.

Ich habe das so interpretiert, dass er den ganzen if-Block und damit auch das wait durch ne Schleife ersetzen will.

Das wait müsste natürlich trotzdem drin bleiben, nur halt while statt if. Dann würde nach dem Aufwachen nicht stupide gehandelt werden, sondern man würde erst nochmal prüfen, ob sich an der Lage was verbessert hat. Falls nicht, dann wird sofort weitergeschlafen.

Mit "schlafen" meine ich übrigens die ganze Zeit "warten", aber sollte imho ersichtlich sein.

Ganon
2012-07-02, 23:10:33
Also wenn man nur einen Verbraucher-Thread hat und einen Erzeuger-Thread hat, funktioniert das Beispiel, da immer nur jeweils ein Thread wartet und auch immer nur ein Thread eine jeweilige Methode aufrufen kann.

Aber schon bei mehr als 2 Threads kracht's ziemlich schnell. Das Verhalten wurde ja von creave gerade ausführlich erklärt.

Senior Sanchez
2012-07-02, 23:16:49
Eure Argumentation mit dem synchronized stimmt so nicht. Es gibt ja nur einen einzigen Monitor, der verteilt wird, und das ist der des "Ablage"-Objekts selbst, auch welchem die ganze Zeit gearbeitet wird.
public synchronized void MethodName() { // hier geht die action } ist ja de facto identisch zu public void MethodName() { synchronized(this) { // hier geht die action } }.

Es bedeutet, dass überhaupt nur ein Thread in einer (nicht in jeder) Methode sein kann. Zu Zeitpunkt X läuft nur ein Thread über den Code, auch wenn ein anderer Thread die jeweils andere Methode aufrufen hätte wollen, der andere Thread darf also nicht mal in die andere Methode rein.

Das sehe ich wiederum in dem Fall anders. Wer sagt denn, dass sich die Threads über dasselbe Objekt synchronisieren? Klar, über this wird synchronisiert, aber wer sagt, dass this bei Verbraucher und Produzent auf dasselbe Objekt verweist? Und empty bzw. buffer können Klassenvariablen sein, sodass alle Threads ohne Probleme darauf zugreifen können.

Ganon
2012-07-02, 23:23:28
Achja, zur "Verbesserung", bzw. Lösung.

Und je nach Aufgabe, kommt man um so ein while-Waiting o.ä. nicht wirklich drumrum. Mit mehreren Erzeugern/Verbrauchern nimmt man dann schlussendlich sowas wie eine Queue und lässt dort parallel Daten rein und rausnehmen. Da man aber dort nicht will, dass einem die Erzeuger den Hauptspeicher zumüllen, wartet man eben so.

Also eine Thread-Safe Queue nimmt einem hier schon viel eigene Arbeit ab. Man muss sich dann nur noch darum kümmern, dass die Queue nicht zu viel Daten bekommt. Man könnte dann quasi den put-Befehl mit einem Mutex belegen und im get dann entscheiden, wann man wieder frei macht.

creave
2012-07-02, 23:27:16
Das sehe ich wiederum in dem Fall anders. Wer sagt denn, dass sich die Threads über dasselbe Objekt synchronisieren? Klar, über this wird synchronisiert, aber wer sagt, dass this bei Verbraucher und Produzent auf dasselbe Objekt verweist? Und empty bzw. buffer können Klassenvariablen sein, sodass alle Threads ohne Probleme darauf zugreifen können.


Wie meinst du das? Dass beide über this synchronisiert werden, ich glaube da sind wir uns einig.

Aber warum sollte this bei Verbraucher und Konsument was anderes sein?

"Ablage ablage = new Ablage()", es gibt nur ein Objekt, ich sehe jetzt gerade nicht warum das aus Sicht des Konsumenten und Produzenten eine andere Referenz sein sollte.

Senior Sanchez
2012-07-03, 00:02:13
Wie meinst du das? Dass beide über this synchronisiert werden, ich glaube da sind wir uns einig.

Aber warum sollte this bei Verbraucher und Konsument was anderes sein?

"Ablage ablage = new Ablage()", es gibt nur ein Objekt, ich sehe jetzt gerade nicht warum das aus Sicht des Konsumenten und Produzenten eine andere Referenz sein sollte.

Das ist eben das Problem. Aus dem obigen Code-Beispiel wird überhaupt nicht deutlich, wie das ganze Programm strukturiert ist. Wenn es so ist, wie du sagst, ist das natürlich dasselbe Objekte aus Sicht des Konsumenten und des Produzenten. Für mich ist das aber eben nicht so eindeutig. Ich gehe davon aus, dass wenn jemand irgendwo im Netz so einen Code, bei dem man ordentlich nachdenken muss, publiziert, dann wird der das auch ein wenig getestet haben und vielleicht in einem Kontext entwickelt haben, den wir hier nicht kennen.

Ganon
2012-07-03, 00:11:16
Ich gehe davon aus, dass wenn jemand irgendwo im Netz so einen Code, bei dem man ordentlich nachdenken muss, publiziert, dann wird der das auch ein wenig getestet haben und vielleicht in einem Kontext entwickelt haben, den wir hier nicht kennen.

Naja, wie ich schon sagte, mit 2 Threads geht alles gut:

http://www.java-samples.com/showtutorial.php?tutorialid=306

Finde da aber z.B. auf die Schnelle keine Warnung, dass das NUR mit 2 Threads gut geht. Mit mehreren knallt es quasi sofort.

mittelding
2012-07-06, 22:21:03
Oha, danke für die viele Resonanz!

Bin wohl gar nicht so unrecht gelegen.