Nahtloser Synchronisationsübergang

Status
Nicht offen für weitere Antworten.
T

tuxedo

Gast
Hallo,

ich hab gerade ein kleines Synchronisationsproblem:

Ich hab eine Klasse die asynchron sendet und empfängt. In der Methode in der gesendet wird, sieht's zur Zeit so aus:

Code:
send(key, packet.getByteBuffer());
		
		// check if need to wait for the result
		synchronized (requestResults) {
			if (requestResults.containsKey(requestID))
				return requestResults.remove(requestID);
		}
		
		// got to sleep until result is present
		synchronized (monitor) {
			try {
				monitor.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}

// result is definitely here, return it
		synchronized (requestResults) {
			return requestResults.remove(requestID);
		}

So. In Zeile 2 wird das Paket zum Senden abgegeben. Der Server empfängt dieses, macht was damit und gibt eine Antwort zurück. Die Antwort wird vom Client der die Anfrage gestellt hat in einem separaten Thread empfangen und in "requestResults" deponiert. Sofort danach wird der wartende "monitor" geweckt.

Ich hab jetzt das Problem, dass in einer lokalen Umgebung, das Antwortpaket zwischen dem ersten synchronized-Block in Zeile 5 und dem zweiten synchronized block in Zeile 11 eintrifft. Folglich wartet sich der Monitor zu tode --> doofe Sache.

Ich hatte auch schon folgende Variante probiert, welche aber zu viel Zeit gekostet hat:

Ich hab ein Monitorobjekt mit einem "Sleep-Flag". Dieses wird gesetzt wenn der Monitor schlafen gelegt wird (also direkt bevor wait() aufgerufen wird).

Aufwecken des Monitors geht dann über "notify()". Aber zuvor wird geschaut ob der Monitor überhaupt schon schläft. Schläft er nicht, wird wenige Millisekunden geschlafen und dann nochmal geschaut ob er nun schläft und aufgeweckt werden kann.

Klar. Funktionieren tut das. Ist aber irgendwie eine Holzhammermethode wenns einem um jede Millisekunde geht. Das Problem betrifft meine SIMON Bibliothek (siege Signatur). Da bin ich um jede zehntel Millisekunde die ein Remote-Methodenaufruf weniger dauert froh.

Nun. Jetzt ist halt die Frage wie ich möglichst ohne eine feste Zeit im Programm zu warten, die Lücke zwischen den beiden synchronized-Blöcken schließen kann.

Any ideas?

- Alex
 

Niki

Top Contributor
Probiers mal so:
Code:
// check if need to wait for the result
     synchronized (monitor) {
      synchronized (requestResults) {
         if (requestResults.containsKey(requestID))
            return requestResults.remove(requestID);
      }
      
      // got to sleep until result is present
     
         try {
            monitor.wait();
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      }

// result is definitely here, return it
      synchronized (requestResults) {
         return requestResults.remove(requestID);
      }
 
T

tuxedo

Gast
Ich probiers mal...

Was ich aber (noch) nicht verstehe:

Wenn ich "monitor" in den Synchronized-Block nehme, dann kann ich doch eigtl nur an einer Stelle im Programm drauf zugreifen weil ich einen Exklusiven Zugriff habe, oder?

Wie kann dann mein Empfangsthread überhaupt "notify" aufrufen? Dafür ist doch auch ein synchronized-block auf den Monitor nötig ...
 
T

tuxedo

Gast
Okay, hab ich rein gemacht. Funktioniert. Aber wo ist da jetzt die "garantie" dass das Ergebnis nicht zwischen dem sync-Block in Zeile 4 und dem Wait in Zeile 12 in die ResultMap rein kommt??

>> mit einem wait() wird das flag des Monitors wieder frei gegeben.

d.h. es wird quasi "außerhalb" des sync-Blocks geschlafen?

- Alex
 

Niki

Top Contributor
Beim senden synchronisierst du auf den Monitor. Das heißt deine Empfangen Methode kann nichts in die Map reinschreiben solange sie nicht den Lock-Flag vom Monitor bekommt. Da das senden auf jeden Fall vor dem empfangen aufgerufen wird bekommt die senden Methode auch auf jeden Fall das Flag vom Monitor. Erst beim wait wird das Flag frei gegeben und die empfangen Methode kann in den synchronized Block des Monitors rein und etwas in die Queue stellen. Sollte also eigentlich genauso funktionieren.
 
T

tuxedo

Gast
Super. Hatte schon fast nen Knoten im Hirn ;-)

Werd das so umsetzen. Danke für den Tipp....

- Alex
 

Marco13

Top Contributor
Bei Thread-Fragen sind alle meine Antworten ohne Gewähr (weil meine Lösungen da irgendwie immer funktionieren, außer an Dienstagen und in Vollmondnächten :roll: ) aber...

1. Du könntest entweder NUR auf den Monitor oder NUR auf die requestResults syncen. Warum du auf beides syncst, ist mir nicht klar
2. (Wichtig) man sollte ein wait() im Allgemeinen NICHT in einem if-Block, sondern in einer Schleife ausführen, die so lange läuft, bis die Bedingung, auf die man wait()et wirklich erfüllt ist. Wenn irgendjemand so frech ist, an einer "falschen" stelle ein notify() auf dem Monitor aufzurufen, dann ist die Aussage "// result is definitely here" definitely falsch.

Insgesamt müßte sowas gehen...

Code:
Result doIt()
{
     send(key, packet.getByteBuffer()); 

     synchronized (requestResults) 
     {
         while (!(requestResults.containsKey(requestID))
         {
             try {
                requestResults.wait();
             } catch (InterruptedException e) {
                e.printStackTrace();
             }
         }
         return requestResults.remove(requestID);
     }
}

void receiveResult()
{
    Result result = getFromServer();

    synchronized (requestResults)
    {
        requestResults.put(result, ...);
        requestResults.notify();
    }
}
 
T

tuxedo

Gast
Marco13 hat gesagt.:
Bei Thread-Fragen sind alle meine Antworten ohne Gewähr (weil meine Lösungen da irgendwie immer funktionieren, außer an Dienstagen und in Vollmondnächten :roll: ) aber...

1. Du könntest entweder NUR auf den Monitor oder NUR auf die requestResults syncen. Warum du auf beides syncst, ist mir nicht klar

Ganz einfach: Ich hab einen ganzen Pool mit Empfangsthreads... D.h. dass mehrere Threads Ergebnisse empfangen und zentral ablegen bis der darauf wartende Bescheid bekommt. Das heisst, ich kann kein Ergebnis aus der Map rausholen ohne Gefahr zu laufen dass schon wieder was rein kommt. Deshlab muss ich auf die ResultMap syncen.

Da aber auch mehrere Methoden auf ein Ergebnis warten können, hab ich für jeden wartenden einen Monitor, und nicht einen Monitor für alle.

2. (Wichtig) man sollte ein wait() im Allgemeinen NICHT in einem if-Block, sondern in einer Schleife ausführen, die so lange läuft, bis die Bedingung, auf die man wait()et wirklich erfüllt ist. Wenn irgendjemand so frech ist, an einer "falschen" stelle ein notify() auf dem Monitor aufzurufen, dann ist die Aussage "// result is definitely here" definitely falsch.

Insgesamt müßte sowas gehen...

Code:
Result doIt()
{
     send(key, packet.getByteBuffer()); 

     synchronized (requestResults) 
     {
         while (!(requestResults.containsKey(requestID))
         {
             try {
                requestResults.wait();
             } catch (InterruptedException e) {
                e.printStackTrace();
             }
         }
         return requestResults.remove(requestID);
     }
}

void receiveResult()
{
    Result result = getFromServer();

    synchronized (requestResults)
    {
        requestResults.put(result, ...);
        requestResults.notify();
    }
}

Das müsste funktionieren. Allerdings werden dann alle wartenden benachrichtigt wenn für EINEN ein Ergebnis da ist. Dürfte doch besser/schneller sein nur den zu wecken der auf das eben empfangene Ergebnis wartet, oder?!

@Niki:
Deine Variante funktioniert bis jetzt bestens.

- Alex
 

Marco13

Top Contributor
Ganz einfach: Ich hab einen ganzen Pool mit Empfangsthreads...
Aahhh :idea: OK, dann macht das ganze wieder Sinn. Aber das mit dem "while" statt "if" solltest du schon noch beachten...
 
T

tuxedo

Gast
Wer kann denn sonst noch den Thread aufwecken? Gibts denn ein globales "alle Monitore wecken"?

- Alex
 

Marco13

Top Contributor
Das nicht, und wenn der monitor z.B. private ist kann eigentlich nichts schiefgehen... außer, wenn es zu einem "spurious wakup" kommt - WANN das passiert, weiß keiner, aber ... näheres dazu (und die Empfehlung, die Bedingung in einer Schleife zu prüfen) steht auch in der Doku zur wait()-methode:
http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Object.html#wait(long)
 
T

tuxedo

Gast
Aufwecken kann man den Monitor NUR über die putResultToMap() Methode. Und die braucht als Parameter die RequestID. Und die hat NUR die Sende-Methode und der Empfangsthread. Zudem sind die IDs "unique".


Was ich aber berücksichtigen sollte ist ein Timeout.
Kann ich irgendwie abfangen wenn ein Monitor mittels "wait(timeoutInMilliSeconds)" schlafen gelegt wurde und wegen des TimeOuts und nicht wegen eines Notifys wieder aufwacht?

Hab in der Doku dazu nix gefunden.

- Alex
 

Marco13

Top Contributor
???:L Häm? Es steht dir ja frei, zu entscheiden, nur wait() zu verwenden und kein wait(timeout) !? Aber falls du das nicht meintest: Eine "direkt" Möglichkeit, bei einem wait(timeout) festzustellen, was die Ursache des Aufweckens war, wüßte ich nicht, aber man kann da bestimmt was basteln.. irgendwas mit Lock & Condition vielleicht ???:L müßt' ich jetzt aber auch erst überlegen...
 
T

tuxedo

Gast
In meinem Fall geht's ohne, denn ich hab schon anderweitig durch mein Programmdesign sichergestellt dass da nix schief gehen kann. Es gibt schlichtweg keine anderen Threads die da dazwischen pfuschen können.

Aber für die weitere verwendung von wait() werd ich mir's merken.

- Alex
 

Marco13

Top Contributor
Es gibt keinen Grund, NICHT while zu verwenden. Aber es gibt potentiell Gründe, es DOCH zu tun.

Code:
if (!nuclearPowerPlant.isCoreMeltdownAvoided())
{
    try 
    { 
        wait(); 
    } 
    catch (InterruptedException e) {}
}
"Das funktioniert auch mit 'if' - wird schon schiefgehen" :wink:
 
T

tuxedo

Gast
Aufwecken kann man bei mir ausschließlich mit der requestID. Und die hat nur die Sende-Methode die auf das Ergebnis mit wait() wartet, und die Empfangsmethode die das Ergebnis anhand der RequestID zuordnen kann. Die IDs sind Unique. Und der Monitor ist "private" und es gibt auch keinen sonstigen "getter" für den Monitor.

Das ist schon atombombensicher und ist mit dem While-Konstrukt gleichzusetzen. Nur mit dem Unterschied dass ich mit einem While alleine nicht auskomme. Mit dem Restlichen wie ich's jetzt hab schon. Sehe keinen Grund eine Tür doppelt gegen einen Atombombenangriff zu sichern.

- Alex
 
Status
Nicht offen für weitere Antworten.

Neue Themen


Oben