Arzt - Patienten [ Threads ]

Hey,

ich habe für die Uni folgende Aufgabenstellung bekommen:

[SPOILER]Ein Patient besucht eine Arztpraxis mit genau einem Behandlungsraum. Er erhält eine Wartenummer und kann den Behandlungsraum erst dann betreten, wenn der Raum frei ist und seine Nummer aufgerufen wurde. Nach einiger Zeit verlässt der Patient den Behandlungsraum. Es kann nur ein Patient zu einer Zeit im Raum behandelt werden. Erstellen Sie die Klassen Patient, Behandlungsraum und ein Testprogramm, das die obige Situation simuliert. Es sollen mehrere Patient-Threads erzeugt und gestartet werden. Die Klasse Behandlungsraum verwaltet die nächste auszugebende Wartenummer, die aktuell aufgerufene Nummer und den Status “besetzt”. Die Klasse enthält die folgenden Methoden: int registrieren() liefert die nächste Wartenummer. Die Registrierung dauert 3 bis 8 Sekunden. void betreten(int nummer) Diese Methode wartet, falls der Raum besetzt ist oder die eigene Wartenummer nicht mit der aufgerufenen Nummer übereinstimmt. Anschließend wird der Raum als besetzt gekennzeichnet und die aufgerufene Nummer um 1 erhöht. void verlassen() Der Raum wird freigegeben und alle wartenden Threads werden geweckt. Nutzen Sie geeignete Synchronosationmechanismen. [/SPOILER]

Ich habe also erstmal die reinen Methoden in Behandlungsraum erstellt und die Klasse Patient erstellt:
Beachten wir also erstmal nicht die synchro.
Ich kann mir nur schwer vorstellen, wie ich einen Patienten dazu bringen soll den Behandlungsraum zu betreten ohne das die Klassen sich gegenseitig komplett kennen.

Ablauf:

**

  1. Patient registriert sich ( Erhält Wartenummer 0 )
  2. Patient 0 betritt Warteraum.
  3. Ein weiterer Patient registriert sich ( Erhält Wartenummer 1 )
  4. Patient 1 will Warteraum betreten. Muss aber warten.
  5. Patient 0 verlässt nach etwas weiterer Zeit den Warteraum und weckt Patient 1 auf.
    **

public class Behandlungsraum implements Runnable

{
	Random r = new Random();
	private int wartenummer = 0;
    private Object lock = new Object();	
    private Patient patient;
    private boolean besetzt = false;
    
	public int registrieren() throws InterruptedException 
	{
		
			int x =  r.nextInt(8000);
			if(x <= 3000)
			{
				x = 3000;
			}
			
			System.out.println("Registriere Patient nr. " + wartenummer);
			System.out.println("Registrieren dauert ca. " + (int) (x / 1000) +  " Sekunden.");
			System.out.println("Registriere Patient[...]");
	        Thread.sleep(x);			
	        System.out.println("Patient erfolgreich mit nr. " + wartenummer +" registriert." + "
");
			return wartenummer++;				
		}
	
		
	public void betreten(int nummer) throws InterruptedException
	{
		if (istBesetzt() || wartenummer != nummer)
		{
			System.out.println("Der Raum ist leider besetzt.");
			// wait
		}	
		
		System.out.println("Der Raum ist jetzt frei.");				
		besetze();
		wartenummer++;
		
	}

	public void verlasse() throws InterruptedException
	{
	
			System.out.println("Patient nr." + wartenummer + " verlässt den Raum.");
			besetzt = false;

	}	
	
	public void besetze()
	{
		besetzt = true;
	}
	public boolean istBesetzt()
	{
		return besetzt;
	}
	
	public void run()
	{
		
	}
	
}
	{		
		private Behandlungsraum a;
		private int wartenummer = 0;	
		
		public Patient(Behandlungsraum a) throws InterruptedException
		{
			wartenummer = a.registrieren();
		}
		
	}

Die Patienten sind ja im Prinzip deine Threads.
Deswegen sollte auch die Klasse Patient das Interface „Runnable“ implementieren - und damit auch die run()-Methode im besitzen.

Zudem heißt es in der Aufgabe ja:

Nutzen Sie geeignete Synchronosationmechanismen.

Stichwort wäre da wohl synchronized, wait() und notify() bzw notifyAll();

Deine betreten-Methode muss zB auf jeden Fall synchronized sein, da ja nur 1Patient das Behandlungszimmer betreten kann.
Falls also ein Thread den Lock kriegt aber nicht die richtige Nummer hat, legt er sich praktisch schlafen. Das auskommentierte wait() ist daher garnicht so schlecht;)
Das if solltest du allerdings zu einem while machen.

In der verlasse-Methode musst du dementsprechend schlafende Threads aufwecken, wie du ja schon erwähnt hattest.

Hoffe das hilft etwas :wink:

Beachten wir also erstmal nicht die synchro.

Das ist mir alles schon bewusst.
Es geht mir mehr um die Ausführung wie ich den Thread Patient dazu bringe den Behandlungsraum zu betreten und dann seine Wartenummer als Information zu übergeben. :S

Explizit weiß ich nicht was ich in die run method´s schreiben soll.

in die run gehoert der Code zum registrieren, dann das betreten und dann der code zum veralssen.

Der Warte Raum verwaltet nur den Zustand.

Dh an den Konstruktor des Patienten, wird nur der Warteraum als Referenz uebergeben.

Und was ist mit dem parameter von betreten ?

Mache ich das nicht schon im Konstruktor von Patient ?

public void run(){
  int wartenummer = registrieren();
  betrete(wartenummer);
  verlassen();
}

Einfach mal so als Grobvorlage

Ich sehe hier noch folgende race-condition :

Was passiert wenn das Registrieren eines Patienten länger dauert als die Behandlung der aktuell aktiven ?
Es kann passieren das ein Patient Nummer 4 bekommt, der Raum aber schon bei Nummer 5 ist. In diesem Fall würde Nummer 4 ewig warten da seine Nummer nicht mehr aufgerufen wird.
Ist zwar in diesem Beispiel bei richtiger Implementierung ausgeschlossen, sollte aber für spätere Multi-Thread-Codes beachtet werden.

Und bei dem Patienten ?
Einfach nur

notify()
?

Würde hier spontan eher notifyAll() nutzen, muss aber zugeben mich noch nie mit wait() und notify() befasst zu haben.

Ich würde nicht auf den Patienten Locken, sondern der Behandlungsraum hat ein PRIVATES lock object, dass niemand anderem als dem Behandlungsraum bekannt ist.

Das ist wichtig dass nur der Behandlungsraum selbst ein notifyAll() aufrufen kann.

also in der Methode betrete des Behandlungsraums kann dann sowas stehen.


  public void betreten(int nummer) throws InterruptedException
    {

 synchronized(lock){
        while(istBesetzt() || wartenummer != nummer)
        {
            System.out.println("Der Raum ist leider besetzt.");
           lock.wait();
        }  
       
        System.out.println("Der Raum ist jetzt frei.");            
        besetze();
        wartenummer++;
       }
    }

Iwie so müsste das aussehen wenn ich das noch richtig in Erinnerung hab.

Was du dir noch überlegen musst, ist,

  • an welcher position nun das NotifyAll aufgerufen werden sollte.
  • ob du das ziehen der Nummer (registrieren) auch synchronisieren musst?
  • wo und wie du das Timing einstellst. Stichwort Thread.sleep();

public void betreten

	{
		System.out.println("Nummer " + wartenummer + " betritt den Raum.");	 
			synchronized(o)
			{
				while (istBesetzt() || wartenummer != nummer)
				{
					System.out.println("
"  + "Der Raum ist leider besetzt." + "
" );
					o.wait();			
				}			
				System.out.println("Der Raum ist frei.");				
				besetze();
				wartenummer++;
			}
	
		
	}```

Ja, dass nofityAll() kommt ja in verlassen, da wenn jemand den Behandlungsraum verlässt alle Threads versuchen sollten als nächster dran zu sein.


> ob du das ziehen der Nummer (registrieren) auch synchronisieren musst?

Das ist ein ein weiteres Problem, da sich die Patienten ja sozusagen die Patienten ja parallel registrieren können sollten, sonst ist der Behandlungsraum ja immer frei.

[SPOILER]Nummer 0 bitte.
Nummer 0 betrit den Raum.
Der Raum ist frei.
Patient nr.0 verlässt den Raum.

Nummer 1 bitte.
Nummer 1 betrit den Raum.
Der Raum ist frei.
Patient nr.1 verlässt den Raum.

Nummer 2 bitte.
Nummer 2 betrit den Raum.
Der Raum ist frei.
Patient nr.2 verlässt den Raum.

Nummer 3 bitte.
Nummer 3 betrit den Raum.
Der Raum ist frei.
Patient nr.3 verlässt den Raum.

Nummer 4 bitte.
Nummer 4 betrit den Raum.
Der Raum ist frei.
Patient nr.4 verlässt den Raum.

Nummer 5 bitte.
Nummer 5 betrit den Raum.
Der Raum ist frei.
Patient nr.5 verlässt den Raum.

Nummer 6 bitte.
Nummer 6 betrit den Raum.
Der Raum ist frei.
Patient nr.6 verlässt den Raum.

Nummer 7 bitte.
Nummer 7 betrit den Raum.
Der Raum ist frei.
Patient nr.7 verlässt den Raum.
[/SPOILER]


aktu:

[SPOILER]

import java.util.Random;

public class Behandlungsraum implements Runnable

{
private int wartenummer = 0;
Random r = new Random();
private int a = 0;
private Object o = new Object();
private Patient patient;
private boolean besetzt = false;

public int registrieren() throws InterruptedException // Gibt die nächste Wartenummer zurück
                                                      // Wer als nächstes dran ist.
    {		    		
	while(true)
	{
		Thread.sleep(wartezeit());	

        System.out.println("Nummer " + wartenummer + " bitte.");	        
		return wartenummer;				
	}
		
	}

	
public void betreten(int nummer) throws InterruptedException
{
	System.out.println("Nummer " + wartenummer + " betritt den Raum.");	 
		synchronized(o)
		{
			while (istBesetzt() || wartenummer != nummer)
			{
				System.out.println("

" + “Der Raum ist leider besetzt.” + "
" );
o.wait();
}
System.out.println(“Der Raum ist frei.”);
besetze();
wartenummer++;
}

}

synchronized void verlasse() throws InterruptedException
{
	
	int b = wartenummer - 1;
			System.out.println("Patient nr." + b + " verlässt den Raum." + "

");
besetzt = false;

			notifyAll();
	
}	

public int wartezeit()
    {
    	int x =  r.nextInt(8000); // Zeit Begrenzung von 3-8 Sekunden.
		if(x <= 3000)
		{
			x = 3000;
		}					
		return x;
    }
public void besetze()
{
	besetzt = true;
}
public boolean istBesetzt()
{
	return besetzt;
}

public void run()
{
	
	while(true)
	{
		try {						
			wartenummer = registrieren();
			betreten(wartenummer);
			Thread.sleep(500);
			verlasse();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}	
	}
			
				
		
	}

	
	
}

[/SPOILER]
for int i = 0 ...
  wz.neuerPatient(patient ...); // in Schleife
  sleep ...
  wz.aufrufPatient(patient ...); // in Schleife
}```

So stelle ich mir das vor. "behandele" fehlt noch. "aufruf" braucht eigentlich keinen Patient en, wt merkt sich ja eine Reihenfolge/prio.

Die zwei Methoden müssen jetzt synchronisiert sein. Das Einfügen dauert etwas und das Aufrufen, ist es leer, ist es nicht leer?, dauert auch etwas.

Sprechzimmer hilfe erstellt neuen Patienten, ins Wartezimmer, an Arzt weiterleiten, und Arzt behandelt Patienten, danach gibt SH Rezept mit.

Das wäre doch der Ablauf, oder? Dann braucht wz noch weitere Methoden.

[quote=Quiji]public int wartezeit() { int x = r.nextInt(8000); // Zeit Begrenzung von 3-8 Sekunden. if(x <= 3000) { x = 3000; } return x; }[/quote]
hier vielleicht nicht so wichtig, für andere Fälle aber, falls noch nicht bewußt:
die Wartezeit ist hier nicht gleichverteilt, zu 3/8 wird 3 sec gewartet, zu 50% 4 sec oder weniger

noch anschaulicher wäre es wenn du 7000 als Minimum nämst, zu 7/8 dann 7 sec, selten etwas im Bereich darüber

faire Zufallsverteilung ist r.nextInt(max-min)+min;

r.nextInt(max-min)+min

Aber wenn max 3 ist und min 5 dann passt das ja nichtmehr :o ( 1000 - 5000 ) + 3000

o stelle ich mir das vor. „behandele“ fehlt noch. „aufruf“ braucht eigentlich keinen Patient en, wt merkt sich ja eine Reihenfolge/prio.

Die zwei Methoden müssen jetzt synchronisiert sein. Das Einfügen dauert etwas und das Aufrufen, ist es leer, ist es nicht leer?, dauert auch etwas.

Sprechzimmer hilfe erstellt neuen Patienten, ins Wartezimmer, an Arzt weiterleiten, und Arzt behandelt Patienten, danach gibt SH Rezept mit.

Das wäre doch der Ablauf, oder? Dann braucht wz noch weitere Methoden.

Ich habe die Aufgabe so verstanden das die methoden in behandlungszimmer vorgegeben sind.
Und die Patienten sollen ja Threads sein.

wenn max kleiner als min ist hat man ganz andere Probleme, was ist eine Zufallszahl größer 5 und gleichzeitig kleiner 3?
mal abgesehen davon dass die eingesetzten Zahlen auch nicht passen:
(max-min)+min zu ( 1000 - 5000 ) + 3000
→ ist max nun 1000 oder 1 (in der Formel) oder 3 oder 3000 (im Text)?, ist min nun 5000 oder 5 (Formel + Text) oder 3 oder 3000 (Formel)?

ein wenig im System muss man schon bleiben, ich weiß nicht wie du auf 3 und 5 kommst, aber mit dem Aufgabenbeispiel 3 bis 8 hat das ja ein wenig zu tun (5 = 8-3),
natürlich ist die 5 auch eine interessante Zahl dabei, durchaus möglich sie irgendwo herzuleiten und einzusetzen,
aber das mit Bedacht und Sinn machen

r.nextInt(8000-3000)+3000;
→ r.nextInt(5000)+3000;

[QUOTE=Quiji]Ich habe die Aufgabe so verstanden das die methoden in behandlungszimmer vorgegeben sind.
Und die Patienten sollen ja Threads sein.[/QUOTE]

Dann behandelt sich der Patient ja teilweise selber, teilweise ist das richtig.

Anfängerfehler :'D Mein Brain-Afk

Dann behandelt sich der Patient ja teilweise selber, teilweise ist das richtig.

Also ich denke mal die Aufgabe möchte das die Patienten Threads die gestarted werden das registrieren() auslösen und dann direkt betreten() wollen.
Wenn der Raum frei ist notify() an an den Patienten Thread der dran ist. Ansonsten wait() an die Patienten() bis verlassen() von einem anderen Patienten aufgerufen wird und somit notifyAll() alle wieder aufweckt. ?

An der umsetzung scheitere ich sehr…

du hast doch schon eine betreten-Methode, die kommt doch ungefähr hin, was konkret funktioniert nicht?

ein notifyAll() zum Ende hin ist freilich noch fehlend, ergänzen?
falls in verlassen(), dann dort noch richtig einbauen und auch verlassen() aufrufen?

die Ausgabe ‘Der Raum ist frei.’ noch vor Aufruf der besetzen()-Methode ist etwas verfrüht,
aber solange nicht auch x ms simuliert verbracht werden kommt das aufs Gleiche hinaus

du hast doch schon eine betreten-Methode, die kommt doch ungefähr hin, was konkret funktioniert nicht?

Es funktioniert soweit, dass der Behandlungsraum ganz ohne die Patienten Threads fungiert.

Hier einmal die Ausgabe :

	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Unknown Source)
	at Patient.run(Patient.java:17)
	at java.lang.Thread.run(Unknown Source)
java.lang.IllegalMonitorStateException
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Unknown Source)
	at Patient.run(Patient.java:17)
	at java.lang.Thread.run(Unknown Source)

// Ist ja klar warum der Fehler kommt 

Nummer 0 bitte.
Nummer 0 betritt den Raum.
Der Raum ist frei.
Patient nr.0 verlässt den Raum.

Nummer 1 bitte.
Nummer 1 betritt den Raum.
Der Raum ist frei.
Patient nr.1 verlässt den Raum.

Nummer 2 bitte.
Nummer 2 betritt den Raum.
Der Raum ist frei.
Patient nr.2 verlässt den Raum.

Also hier einmal der momentane Code:

import java.util.Random;

public class Behandlungsraum implements Runnable

{
	private int wartenummer = 0;
	Random r = new Random();
	private int a = 0;
	private Object o = new Object();
    private Patient patient;
    private boolean besetzt = false;
    
   
	public int registrieren() throws InterruptedException // Gibt die nächste Wartenummer zurück
	                                                      // Wer als nächstes dran ist.
	    {		    		
		while(true)
		{
			Thread.sleep(r.nextInt(5000)+3000);	

	        System.out.println("Nummer " + wartenummer + " bitte.");	        
			return wartenummer;				
		}
			
		}
	
		
	public void betreten(int nummer) throws InterruptedException
	{
		System.out.println("Nummer " + wartenummer + " betritt den Raum.");	 
			synchronized(o)
			{
				while (istBesetzt() || wartenummer != nummer)
				{
					System.out.println("
"  + "Der Raum ist leider besetzt." + "
" );
					o.wait();			
				}			
				System.out.println("Der Raum ist frei.");				
				besetze();
				wartenummer++;
			}
	
		
	}

	public void verlasse() throws InterruptedException
	{
		synchronized(o)
		{
			while(istBesetzt())
			{
				int b = wartenummer - 1;
				System.out.println("Patient nr." + b + " verlässt den Raum." + "
");
				besetzt = false;
		
				o.notifyAll();
			}
		
		}
		
		
	}	
	

	public void besetze()
	{
		besetzt = true;
	}
	public boolean istBesetzt()
	{
		return besetzt;
	}
	
	public void run()
	{
		
		while(true)
		{
			try {						
				wartenummer = registrieren();
				betreten(wartenummer);
				Thread.sleep(500);
				verlasse();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}	
		}
				
					
			
		}
	
		
		
	}
	
	{		

	Behandlungsraum a;
	
	public Patient(Behandlungsraum b)
	{
		a = b;
	}
	
		public void run() 
		
		{
			while(true)
			{
				try {
					wait();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				notify();
			}
			
		}
		
	}
public class Terminal {

	public static void main(String[] args) throws InterruptedException 
	
	{
	  Behandlungsraum b = new Behandlungsraum();
	  Patient p1 = new Patient(b);
	  Patient p2 = new Patient(b);
	 Thread t1 = new Thread(b);
	 Thread t2 = new Thread(p1);
	 Thread t3 = new Thread(p2);
	  
	 t1.start();
	 t2.start();
	 t3.start();

	}

}

wenn die Methode nicht aufgerufen/ nicht verwendet wird, dann kann nicht so wirklich von Funktionieren sprechen

der Fehler kommt vom wait() des Patienten ohne synchronized-Monitor,
falls dir das klar ist, wieso ist er dann noch da? ich habe nicht alles hier verfolgt, aber ist glaube ich nicht die aktuelle Diskussion?

wieso wartet der Patient überhaupt für sich extra mit wait()? dafür gibt es keinen Anlass,
in Patient muss es einen registrieren()-Aufruf geben, den hattest du im ersten Posting sogar schon,
run-Methode dafür besser als Konstruktor, um nicht Aufrufer zu blockieren,
und als zweites betreten()-Aufruf

zwei einfache Dinge, einfach reinschreiben, die Methoden kümmern sich dann jeweils um Synchronisation & Co.

edit:
Behandlungsraum kein Runnable/ Thread!
dessen run kommt für Patient gut hin