Kritischer Abschnitt / Monitor

Hi,

ich habe in einer Client/Server-Anwendung eine Konfiguration, die zu einer Zeit nur ein Thread verändern darf. Um dies sicherzustellen habe ich ein Singleton und dort zwei Methoden zum Betreten und Verlassen des kritischen Bereichs. IMHO ist meine Implementierung richtig und besteht auch verschiedene Tests, doch im Praxiseinsatz kommt es scheinbar immer wieder und leider nicht reproduzierbar zu Deadlock-Situationen.

Evtl. entdeckt jemand durch bloßes Blinzeln mit den Augen gleich einen Fehler:

public class MyMonitor
{
 private static MyMonitor m\_Instance;
 private Object m\_Monitor = new Object();
 private boolean m\_MonitorTaken = false;

 private MyMonitor(){}

 public synchronized static MyMonitor getInstance()
 {
 if(m\_Instance == null)
 {
 m\_Instance = new MyMonitor();
 }
 return m\_Instance;
 }

 public void enterMonitor() throws InterruptedException
 {
 synchronized(m\_Monitor)
 {
 while(m\_MonitorTaken)
 {
 m\_Monitor.wait(1000);
 }
 m\_MonitorTaken = true;
 }
 }

 public void exitMonitor()
 {
 synchronized(m\_Monitor)
 {
 m\_MonitorTaken = false;
 m\_Monitor.notifyAll();
 }
 }
}

Und dann ein paar Aufrufe dazue, die parallel auftreten können:

...
try
{
 MyMonitor.getInstance().enterMonitor();
 ...
 // die Zeit verstreicht
 ...
}
finally
{
 MyMonitor.getInstance().exitMonitor();
}

Anhand von Logausgaben sehe ich, dass ein Thread den Monitor betritt und nicht wieder verlässt, ein anderer Thread am wait() blockiert.
Dies ist aber, wie gesagt, leider nicht reproduzierbar. In meinen Tests funktioniert alles tadellos :frowning:

Danke für Tipps
Ciao

Moien

Evtl. entdeckt jemand durch bloßes Blinzeln mit den Augen
gleich einen Fehler:

Ja. Du verläst dich blind darauf dass der Benutzer der Klasse das enter und exit aufruft.

Ich bau mal ein bisschen Debug-code rein, ja ?

public class MyMonitor
{
 private static MyMonitor m\_Instance;
 private Object m\_Monitor = new Object();
 private boolean m\_MonitorTaken = false;


 private Thread inside = null;

 //wann war der letzte erfolgreiche Zutritt ? 
 private long lastenter = 0;

 private MyMonitor(){}

 public void enterMonitor() throws InterruptedException
 {
 synchronized(m\_Monitor)
 {
 while(m\_MonitorTaken)
 {
 if ((lastenter != 0) 
 && (lastenter + 2000 \> System.currentTimeMillis()){
 System.out.println ("Thread "+inside+" hat evtl. vergessen sich abzumelden");
 //wenn du die Zeiten exakt kennst und die immer 
 //eingehalten werden kann man hier m\_MonitorTaken 
 //zurücksetzen.
 }
 m\_Monitor.wait(1000);
 }
 m\_MonitorTaken = true;
 inside = Thread.currentThread();
 lastenter = System.currentTimeMillis();
 }
 }

 public void exitMonitor()
 {
 synchronized(m\_Monitor)
 {
 //der Test kommt in ALLE geschützten Methode rein:
 if (inside != Thread.currentThread()){
 //ganz grosser Stacktrace mit ganz 
 //viel "hier ist was faul Ausgaben"
 }
 m\_MonitorTaken = false;
 inside = null;
 m\_Monitor.notifyAll();
 }
 }
}

Anhand von Logausgaben sehe ich, dass ein Thread den Monitor
betritt und nicht wieder verlässt

Da fehlt dann das exit.

ein anderer Thread am wait() blockiert.

Wahrscheinlich nachdem ein anderer Thread das exit vergessen hat.

cu

leider nicht ganz :frowning:
Hi pumpkin,

leider oder besser gesagt zum Glück sind all meine Aufrufe in try/finally, d.h. erste Zeile im try macht den enterMonitor() und im finally ist nur der exitMonitor() drin.

Definitiv sind im Code 4 Stellen, an denen dieser kritische Abschnitt betreten wird. Ich wollte an diesen 4 Stellen lediglich ausschließen, dass sich einer der 4 Aufrufe gleichzeitig mit einem der anderen 3 ins Gehege kommt. Alle Aufrufe kommen via RMI an die Server-Applikation.

Vielleicht liegt ja auch der der Haken?!

Wie gesagt. Der Fehler tritt nur sporadisch auf und ist nicht reproduzierbar. Deine Idee mit dem lastenter ist nicht schlecht. Vielleicht hilft dies ja weiter.

Eine andere Möglichkeit wäre, dass ich den Monitor selbst in einer Methode nach außen gebe und dann den kritischen Bereich direkt durch nen synchronized-Block absichere, also nicht wie bisher

try
{
 MyMonitor.getInstance().enterMonitor();
 ...
}
finally
{
 MyMonitor.getInstance().exitMonitor();
}

sondern jetzt ein

synchronized(MyMonitor.getInstance())
{
 ...
}

Das sollte ja das gleiche bewirken?!

Ciao, Bill

Moien

leider oder besser gesagt zum Glück sind all meine Aufrufe in
try/finally, d.h. erste Zeile im try macht den enterMonitor()
und im finally ist nur der exitMonitor() drin.

glaub es mir: such nochmal.

Definitiv sind im Code 4 Stellen, an denen dieser kritische
Abschnitt betreten wird. Ich wollte an diesen 4 Stellen
lediglich ausschließen, dass sich einer der 4 Aufrufe
gleichzeitig mit einem der anderen 3 ins Gehege kommt. Alle
Aufrufe kommen via RMI an die Server-Applikation.

Oh shit. Du bist evtl. viel, viel tiefer in Problemen als du denkst.

Remote-Objecte werden an die Klients geschickt und lokal auf dem Client aufgerufen/ausgeführt. Der Server schreibt mit was mit dem Ding gemacht wird (bye-bye schnell synchronized ausführen)

Bei UnicastObjecte werden die Methodenaufrufe zum Server gebracht und auf dem Server ausgeführt (bye-bye performance)

Serialisable Objecte werden von Server rausgegeben und nicht weiter verfolgt (bye-bye singelton)

Wenn du die 3 Objecttypen mixed bekommst du in null-komma-nix ein absolut unüberschaubares Geflecht. Und die Bugliste vom rmiserver-code in java macht die Sache nicht einfacher.

Eine andere Möglichkeit wäre, dass ich den Monitor selbst in
einer Methode nach außen gebe und dann den kritischen Bereich
direkt durch nen synchronized-Block absichere, also nicht wie
bisher

try
{
MyMonitor.getInstance().enterMonitor();

}
finally
{
MyMonitor.getInstance().exitMonitor();
}

sondern jetzt
ein

synchronized(MyMonitor.getInstance())
{

}

Das sollte ja das gleiche bewirken?!

Das tut was gar lustiges, lösten aber das Problem nicht. Synchroniziationen auf Stubs werden nicht über´s Netz verteilt…

cu