Zum Inhalt springen

Multithreading: 2 Threads wechseln sich ab


Empfohlene Beiträge

Geschrieben

Hallo,

im Zuge meiner Vorbereitung auf den SCJP prügel ich mir das Multithreading rein und komme bei einem kleinen Übungsbeispiel nicht weiter.

Programmlogik:

Ich habe 2 Klassen, die Runnable implementieren. Die eine stellt einen Postboten dar, der 10 mal ankommt und Post abliefert. Die andere Klasse ist der Empfänger, der 10 mal zum Briefkasten rennt und die Post abholt. Das gewünschte Ergebnis ist nun wie man vermuten könnte "Postbote kommt, Empfänger kommt, Postbote kommt, Empfänger kommt, usw."

Soweit hab ich es:


public class PostMan implements Runnable

{

	public void run() 

	{

		for (int i = 0; i < 10; i++)

		{

			act();

		}

	}


	synchronized void act()

	{

		System.out.println("Bote: Liefere Post ab");

	//	try{ notify(); wait(); } catch (Exception e) {}

	}

}


public class Receiver implements Runnable 

{

	public void run() 

	{

		for (int i = 0; i < 10; i++)

		{

			act();

		}

	}


	synchronized void act()

	{

		System.out.println("Empfänger: Hole Post ab");

	//	try{ notify(); wait(); } catch (Exception e) {}

	}

}


public class ThreadTest 

{

	public static void main (String [] args)

	{

		Thread  postman = new Thread(new PostMan());

		Thread  rec     = new Thread(new Receiver());


		postman.start();

		rec.start();

	}

}

Wie verwende ich hier wait() und/oder notify(), um den jeweils anden Thread aus dem waiting-Pool zu holen und den aktuellen reinzusetzen? Wenn ich den jeweils auskommentierten try/catch Block einkommentiere, gibt es stets einen Deadlock. So, wie es jetzt ist, kommen Bote und Empfänger 10x zufällig vorbei - das würde ich gern steuern.

Kann wer helfen?

Geschrieben

Gibts dafür ned eine Hashtable (?)

Die benutze ich z.B. um Clients "abzusetzen", damit diese die nachrichten vom Server bekommen. Sofern ein Client disconnected, so wird dieser wieder aus der Hashtable entfernt.

Du musst halt Definieren "Du bist angekommen, warte noch kurz auf den anderen".

Geschrieben

Ich werfe einmal die Begriffe "Dead Lock" und "statische / synchronisierte Objekte" in den Raum. Du brauchst für Dein Beispiel ein Fähnchen an dem Briefkasten, dass der Postbote, wenn was da ist hoch klappt und derjenige, der die Post holt wieder runter klappt. Wobei immer nur einer das Fähnchen betätigen kann

Geschrieben

An dieses Fähnchen hab ich auch schon gedacht, hier mal ein neuer Ansatz mit anschließender Ausgabe:

  • Der Postbote

public class PostMan extends Thread

{

	public void run() 

	{

		for (int i = 0; i < 10; i++)

		{

			System.out.println("Bote: Bringe Post");


			try 

			{ 

				Thread.sleep(500); 

			} 

			catch (InterruptedException e) 

			{ 

				e.printStackTrace(); 

			}


			if (MailBox.hasMail() == false)

				act();

			else

				System.out.println("Da liegt noch was vom letzten Mal");

		}

	}


	private synchronized void act()

	{

		System.out.println("Bote: Sie haben Post!");

		MailBox.deliverMail();

	}

}
  • Der Empfänger
public class Receiver extends Thread 

{

	public void run() 

	{

		for (int i = 0; i < 10; i++)

		{

			System.out.println("Empfänger: Schaue nach Post");

			try 

			{

				Thread.sleep(500);

			} 

			catch (InterruptedException e) 

			{

				e.printStackTrace();

			}



			if (MailBox.hasMail())

				act();

			else

				System.out.println("Empfänger: Umsonst gelaufen, nichts da.");

		}

	}


	private synchronized void act()

	{

		System.out.println("Empfänger: Juhu, Post ist da");

		MailBox.getMail();

	}

}

  • Der Briefkasten
public class MailBox 

{

	private static boolean mail = true;


	public static boolean hasMail()

	{

		return mail;

	}


	public static void getMail()

	{

		System.out.println("Empfänger: Post abgeholt");

		mail = false;

		System.out.println();

	}


	public static void deliverMail()

	{

		System.out.println("Bote: Post eingeworfen");

		mail = true;

		System.out.println();

	}

}

  • Die Ausgabe

Bote: Bringe Post

Empfänger: Schaue nach Post

Empfänger: Juhu, Post ist da

Empfänger: Post abgeholt

Empfänger: Schaue nach Post

Bote: Da liegt noch was vom letzten Mal

Bote: Bringe Post

Empfänger: Umsonst gelaufen, nichts da.

Empfänger: Schaue nach Post

Bote: Sie haben Post!

Bote: Post eingeworfen

Bote: Bringe Post

Bote: Da liegt noch was vom letzten Mal

Bote: Bringe Post

Empfänger: Juhu, Post ist da

Empfänger: Post abgeholt

So ganz richtig ist die Ausgabe noch nicht, und ich kriegs ums Verrecken nicht hin, das sauber zu koordinieren, wo ist der Haken?

Geschrieben

Sorry für den Doppelpost.

Ich habe in der Klasse MailBox die Methoden synchronisiert und der Ablauf scheint nun nachvollziehbar und plausibel zu werden. Hier nochmal eine Ausgabe, die deutlich macht, wann welcher Thread einsetzt.

Bote: (1) Bringe Post

Empfänger: (1) Schaue nach Post

Empfänger: (2) Umsonst gelaufen, nichts da.

Empfänger: (1) Schaue nach Post

Bote: (2) Briefkasten leer, werfe Brief ein

Bote: (3) Post eingeworfen

Bote: (1) Bringe Post

Empfänger: (2) Juhu, Post ist da

Empfänger: (3) Post abgeholt

Empfänger: (1) Schaue nach Post

Bote: (2) Briefkasten ist voll, werfe nichts ein

Bote: (1) Bringe Post

Empfänger: (2) Umsonst gelaufen, nichts da.

Bote: (2) Briefkasten leer, werfe Brief ein

Bote: (3) Post eingeworfen

Bote: (1) Bringe Post

Mein Problem ist jetzt noch diese Gameboy-Logik - ich möchte diese Verschachtelung verhindern. Ein Thread darf erst dann loslegen, wenn der jeweils andere fertig ist, hier also, dass einer der beiden den Briefkasten erst anrührt, wenn der jeweils andere nicht mehr dran ist. Ich stell mich wieder schön blöd an, hoffentlich sieht jemand, was ich ändern muss

:old

Danke

Geschrieben
Ein Thread darf erst dann loslegen, wenn der jeweils andere fertig ist, hier also, dass einer der beiden den Briefkasten erst anrührt, wenn der jeweils andere nicht mehr dran ist.

Bist doch schon auf dem richtigen Weg. Stcihwort "Dead-Lock" und dabei mit den "Semaphoren" aufpassen:

Thread 1 versucht zu locken, wenn ja, Briefkasten füllen, Lock frei geben, andernfalls warten

Thread 2 versucht zu locken, wenn ja, Briefkasten leeren, Lock frei geben, andernfalls warten

Geschrieben

So, ich habs nun folgendermaßen gelöst: Beide Threads bekommen ein Objekt Mailbox mit in den Konstruktor und sperren dies für den jeweils anderen Thread, sobald sie darauf zugreifen. Da Thread B aber erst sinnvoll handeln kann, wenn Thread A durchlaufen ist, wartet dieser, falls nötig. A kann analog nicht 2x nacheinander sinnvoll handeln und wartet in diesem Fall auf einen neuen Durchlauf von B.

Zur Vollständigkeit nochmal der Code:

public class PostMan extends Thread

{

	MailBox mailbox;

	public PostMan (MailBox mailbox)

	{

		this.mailbox = mailbox;

	}


	public void run() 

	{

		for (int i = 0; i < 10; i++)

		{

			synchronized(mailbox)

			{

				System.out.println("Bote: (1) Bringe Post");

				while (mailbox.hasMail())

				{

					System.out.println("Bote: (2) Briefkasten ist voll, warte bis jemand kommt");

					try 

					{      mailbox.wait();       } 

					catch (InterruptedException e) 

					{      e.printStackTrace();  }

				}

				act();

				mailbox.notify();

			}

		}

	}


	private void act()

	{

		System.out.println("Bote: (2) Briefkasten leer, werfe Brief ein");

		mailbox.deliverMail();

	}

}


public class Receiver extends Thread 

{

	MailBox mailbox;

	public Receiver (MailBox mailbox)

	{

		this.mailbox = mailbox;

	}


	public void run() 

	{

		for (int i = 0; i < 10; i++)

		{

			synchronized(mailbox)

			{

				System.out.println("Empfänger: (1) Schaue nach Post");

				while (mailbox.hasMail() == false)

				{

					System.out.println("Empfänger: (2) Nichts drin, dann warte ich noch.");

					try 

					{      mailbox.wait();       } 

					catch (InterruptedException e) 

					{     e.printStackTrace();   }

				}

				act();

				mailbox.notify();

			}	

		}

	}


	private synchronized void act()

	{

		System.out.println("Empfänger: (2) Juhu, Post ist da");

		mailbox.getMail();

	}

}


public class MailBox 

{

	private boolean mail = false;

	public synchronized boolean hasMail()

	{

		return mail;

	}


	public synchronized void getMail()

	{

		System.out.println("Empfänger: (3) Post abgeholt");

		mail = false;

		System.out.println();

	}


	public synchronized void deliverMail()

	{

		System.out.println("Bote: (3) Post eingeworfen");

		mail = true;

		System.out.println();

	}

}


public class ThreadTest 

{

	public static void main (String [] args)

	{

		MailBox mailbox = new MailBox();

		Thread  postman = new PostMan(mailbox);

		Thread  rec     = new Receiver(mailbox);


		postman.start();

		rec.start();

	}

}


Programmausgabe:

Empfänger: (1) Schaue nach Post

Empfänger: (2) Nichts drin, dann warte ich noch.

Bote: (1) Bringe Post

Bote: (2) Briefkasten leer, werfe Brief ein

Bote: (3) Post eingeworfen

Bote: (1) Bringe Post

Bote: (2) Briefkasten ist voll, warte bis jemand kommt

Empfänger: (2) Juhu, Post ist da

Empfänger: (3) Post abgeholt

Bote: (2) Briefkasten leer, werfe Brief ein

Bote: (3) Post eingeworfen

Bote: (1) Bringe Post

Bote: (2) Briefkasten ist voll, warte bis jemand kommt

Empfänger: (1) Schaue nach Post

Empfänger: (2) Juhu, Post ist da

Empfänger: (3) Post abgeholt

Danke für die Hilfe!

Geschrieben
So, ich habs nun folgendermaßen gelöst: Beide Threads bekommen ein Objekt Mailbox mit in den Konstruktor und sperren dies für den jeweils anderen Thread, sobald sie darauf zugreifen.

Ich verweise einmal darauf Deadlock ? Wikipedia

Du solltest prüfen, ob Deine Implementierung nicht einen Dead Lock erzeugen kann, z.B. wenn ein Thread beendet wird und den Lock nicht aufheben kann, Stichwort Watchdog ? Wikipedia

Geschrieben

Danke für die Hinweise, auf das Problem bin ich unterwegs tatsächlich einige Male gestoßen - insbesondere in solchen Fällen, wo wait() und notify() nicht an der richtigen Stelle aufgerufen wurden (mitunter erhielt ich dann eine IllegalMonitorStateException).

Dieses Dilemma meine ich aber - und ich bitte um Kritik - hier umgangen zu haben:


while (mailbox.hasMail() == false)

{

	try 

	{      mailbox.wait();       } 

	catch (InterruptedException e) 

	{     e.printStackTrace();   }

}

act();

mailbox.notify();

Solange die Bedingung der Schleife zutrifft, wird über das Mailbox-Objekt der derzeit aktive Thread auf "wartend" gesetzt und der Zugriff darauf freigegeben, was den jeweils Anderen (der ja bereit ist) veranlasst, aktiv zu werden. In diesem ist die Schleifenbedingung natürlich entgegengesetzt definiert, sodass dort gleich act(); aufgerufen und der wartende Thread anschließend benachrichtigt werden kann (der dann die while-Schleife verlassen und seinerseits arbeiten kann).

Mir scheint, dass so alles sauber ineinander greift.

Geschrieben

Im Moment würden Deine Threads den CPU völlig blockieren, da Du innerhalb der Routine eine for-Schleife komplett "ohne Rücksicht" auf Verluste durchlaufen lässt. Normalerweise pausiert man Thread immer nachdem sie einmal durchgelaufen sind, damit andere Threads auch die Möglichkeit bekommen Daten zu verarbeiten

Ein Dead Lock kann aber immer noch auftreten:

Wenn die Thread Schleife im letzten Durchlauf aus PostMan durchlaufen wird und der Thread das notify ausgeführt hat, befindet sich Post in der Box.

Wenn die For Schleife des Receiver < 8 ist, dann würde bei der 8. Iteration der Kasten geleert werden, aber in der 9. die while-Schleife ausführen, da hasMail false ist ( man macht aber bei den Booleanvergleichen kein == false, das ist unnötig ). Der Receiver bleibt dann somit im wait hängen und erhält kein notify mehr, damit ist der Receiver ein Zombi-Thread geworden und wird nicht mehr beendet

Geschrieben

Ich muss gestehen, deinen Einwand nicht nachvollziehen zu können

Normalerweise pausiert man Thread immer nachdem sie einmal durchgelaufen sind, damit andere Threads auch die Möglichkeit bekommen Daten zu verarbeiten

Dies hab ich so aber gar nicht beabsichtigt, da ich es logischer und sinnvoller finde (um mal den Praxisbezug zu halten), dass der Empfänger nicht dauernd grundlos zum leeren Postkasten rennt, sondern eben dort wartet, bis der PostMan winkt und mittels notify sagt, "Ok, Post is da, du bist wieder runnable".

Damit kann ich auch beim besten Willen nicht erkennen, wo dieser Fall:

Dead Lock (...)

Wenn die Thread Schleife im letzten Durchlauf aus PostMan durchlaufen wird und der Thread das notify ausgeführt hat, befindet sich Post in der Box.

Wenn die For Schleife des Receiver < 8 ist, dann würde bei der 8. Iteration der Kasten geleert werden, aber in der 9. die while-Schleife ausführen, da hasMail false ist

... auftreten kann. Die beiden For-Schleifen sind stets maximal eine Iteration auseinander und wenn das der Fall ist, wird gewartet, hier ein Indiz dafür:

Bote: 8. Durchlauf

Bote: (1) Bringe Post

Bote: (2) Briefkasten ist voll, warte bis jemand kommt

Empfänger: (2) Juhu, Post ist da

Empfänger: (3) Post abgeholt

Empfänger: 8. Durchlauf

Empfänger: (1) Schaue nach Post

Empfänger: (2) Nichts drin, dann warte ich noch.

Bote: (2) Briefkasten leer, werfe Brief ein

Bote: (3) Post eingeworfen

Bote: 9. Durchlauf

Bote: (1) Bringe Post

Bote: (2) Briefkasten ist voll, warte bis jemand kommt

Empfänger: (2) Juhu, Post ist da

Empfänger: (3) Post abgeholt

Bote: (2) Briefkasten leer, werfe Brief ein

Bote: (3) Post eingeworfen

/*Hier kommt die interessante Stelle. Wenn PostMan nicht warten

und seinen 10. Durchlauf beenden würde, würde Receiver mit dem

9. Durchlauf (völlig richtig) sich auf wait setzen und für immer warten.*/

Bote: 10. Durchlauf

Bote: (1) Bringe Post

Bote: (2) Briefkasten ist voll, warte bis jemand kommt

Empfänger: 9. Durchlauf

Empfänger: (1) Schaue nach Post

Empfänger: (2) Juhu, Post ist da

Empfänger: (3) Post abgeholt

Bote: (2) Briefkasten leer, werfe Brief ein

Bote: (3) Post eingeworfen

Empfänger: 10. Durchlauf

Empfänger: (1) Schaue nach Post

Empfänger: (2) Juhu, Post ist da

Empfänger: (3) Post abgeholt

Ich behaupte auch, dass das Programm immer mit/nach dem Empfänger-Thread beendet wird. Auch wenn dieser im ersten Durchlauf "vorangeht", muss er den PostMan vorlassen, weil er (ohne dessen Resultat) nicht weiter machen kann. Wie gesagt, ich erkenne da den Punkt nicht, wo ein Dead Lock entstünde, wenn dieser Wechsel stets klappt.

Geschrieben

Dies hab ich so aber gar nicht beabsichtigt, da ich es logischer und sinnvoller finde (um mal den Praxisbezug zu halten), dass der Empfänger nicht dauernd grundlos zum leeren Postkasten rennt, sondern eben dort wartet, bis der PostMan winkt und mittels notify sagt, "Ok, Post is da, du bist wieder runnable".

Nein genau das macht man eben nicht. Wenn Du nicht einen oder zwei Threads hast, sondern 1000 dann ginge irgendwann nichts mehr, weil jeder Thread non stop Resoucen belegt. Lass man Deine Schleife z.B. 1.000.000 mal durchlaufen.

... auftreten kann. Die beiden For-Schleifen sind stets maximal eine Iteration auseinander und wenn das der Fall ist, wird gewartet, hier ein Indiz dafür:

Du denkst hier sequentiell. Ein Thread ist etwas völlig eigenständiges. Du hast hier keinen Flip-Flop der eben deterministisch läuft. Ein Thread ist gedanklich durch das OS bestimmt, das reserviert einen Time Slot für den Thread, nur inwieweit diese eben in Deiner gedanklichen Weise ablaufen ist nicht klar. Z.B. kann es ja sein, dass ein anderer Thread mit höherer Priorität zwischen Deine schiebt und schon laufen die Schleifen auseinander

Geschrieben
Wenn Du nicht einen oder zwei Threads hast, sondern 1000 dann ginge irgendwann nichts mehr (...) und schon laufen die Schleifen auseinander

In Ordnung, verstehe. Für eine solche Anforderung ist meine Implementierung dann warscheinlich nicht geeignet (was mich ein bisschen neugierig auf einen alternativen Vorschlag macht). Grundsätzlich wollte ich aber in der Tat nur ein einfaches Praxisbeispiel zur "Steuerung" von 2 Threads programmieren, die mit wait() und notify() arbeitet. Und auch, wenn ich jetzt in Gefahr laufe, Punktabzug für den sauberen Stil zu bekommen - für 2 (und nur 2) Threads ist es doch korrekt gelöst. (?)

Wie gesagt, dies soll lediglich die Theorie für den SCJP festigen und danke nochmals für die Hilfestellung bis hierhin.

Geschrieben
Und auch, wenn ich jetzt in Gefahr laufe, Punktabzug für den sauberen Stil zu bekommen - für 2 (und nur 2) Threads ist es doch korrekt gelöst. (?)

Ich hätte da etwas Bauchschmerzen, der Grundgedanke ist auf jeden Fall richtig. Nur halt dass es zu einem Thread gehört "wenn er nichts zu tun hat" ihn schlafen zu schicken und genau das fehlt. Wäre aber gar nicht mal schwer da zu ergänzen.

Geschrieben
Nur halt dass es zu einem Thread gehört "wenn er nichts zu tun hat" ihn schlafen zu schicken und genau das fehlt. Wäre aber gar nicht mal schwer da zu ergänzen.

Und das meine ich ja getan zu haben. Wenn nichts zu tun, warte, wenn wieder aktiv, prüfe Bedingung, noch nicht erfüllt ? weiter warten, sonst leg los. Entweder wir reden aneinander vorbei oder ich seh/verstehe hier den fehlenden Punkt einfach nicht.

Sollte sich dein Einwand auf obige Schleife beziehen, bitte mal abändern oder deutlich mit dem Finger drauf zeigen.

Geschrieben (bearbeitet)
Und das meine ich ja getan zu haben.

Nein eben nicht

Wenn nichts zu tun, warte, wenn wieder aktiv, prüfe Bedingung, noch nicht erfüllt ? weiter warten, sonst leg los.

Du startest den Thread, wenn nichts da heißt die Aktion "warten", d.h. der Thread macht konkret etwas, er "wartet", d.h. er ist aktiv. Du blockierst eben nur seine Ausführung.

Ich meine aber, der Thread soll für eine bestimmte Zeit "deaktiviert" werden, d.h. er schaut noch ob etwas da ist, wenn nicht, legt er sich für eine bestimmte Zeit schlafen. Entweder kann er dann durch einen Prozess aufgeweckt werden oder er wird selbst nach der Zeit wieder aktiv.

Der Unterschied ist, dass Dein Thread eben anderen Threads nicht den Vorrang lässt, er wartet eben so lange, bis er dran ist, eine Nebenläufigkeit existiert bei Dir nicht, da Du eine Abhängigkeit erzeugt hast. Du setzt "warten" mit "deaktivieren / schlafen" gleich und das ist falsch, weil Du damit keine Nebenläufigkeit erzeugen kannst, wie sie bei der Threadprogrammierung gemacht werden. Man schafft unabhängige Threads, die nur an gewissen stellen eben synchronisiert werden müssen.

Dein Beispiel ist immer noch sequentiell, das ganze würde sich auch über eine Schleife mit sequentiellen Ablauf realisieren machen. Der Kern beim Multithreading ist eben die Nebenläufigkeit der Thread / Parallelisierung der Aufgabe

Bearbeitet von flashpixx
Geschrieben
eine Nebenläufigkeit existiert bei Dir nicht, da Du eine Abhängigkeit erzeugt hast

Das Sequentielle ist in diesem Fall doch aber unumgänglich (und sogar gewollt). Ein Thread schafft eine Bedingung für den Anderen und umgekehrt. Dass die Nebenläufigkeit hier "ausgehebelt" oder unterdrückt ist, ist doch eine Folge daraus. Ansonsten führte es ja zu dem Zustand ganz am Anfang des Threads, dass beide (nebeneinander) aktiv sind, was zu einen unvorhersehbaren Ablauf führt - ohne das "Fähnchen". Oder seh ich das falsch? Vielleicht musst du Java sprechen, damit ich es kapier. :beagolisc

Geschrieben
Das Sequentielle ist in diesem Fall doch aber unumgänglich (und sogar gewollt). Ein Thread schafft eine Bedingung für den Anderen und umgekehrt.

Nein, das muss es nicht sein, genau das ist ja der Punkt beim Multithreading.

Ansonsten führte es ja zu dem Zustand ganz am Anfang des Threads, dass beide (nebeneinander) aktiv sind, was zu einen unvorhersehbaren Ablauf führt - ohne das "Fähnchen".

Das ist ja genau Deine Aufgabe es so zu machen, dass es funktioniert.

Wenn ich Deine Interpretation der Threadaufgabe etwas allgemeiner formuliere, dann wäre es nie möglich irgendetwas zu parallelisieren, denn letztendlich ist jede Aufgabe an irgendeinem Punkt sequentiell, spätestens wenn Daten zusammen gefasst werden.

Das ganze Problem lässt sich auch wirklich parallel verarbeiten und muss immer nur genau an einem Punkt sequentiell durchgeführt werden.

Dein Kommentar

Du kannst jetzt schreiben und Dich später registrieren. Wenn Du ein Konto hast, melde Dich jetzt an, um unter Deinem Benutzernamen zu schreiben.

Gast
Auf dieses Thema antworten...

×   Du hast formatierten Text eingefügt.   Formatierung wiederherstellen

  Nur 75 Emojis sind erlaubt.

×   Dein Link wurde automatisch eingebettet.   Einbetten rückgängig machen und als Link darstellen

×   Dein vorheriger Inhalt wurde wiederhergestellt.   Editor leeren

×   Du kannst Bilder nicht direkt einfügen. Lade Bilder hoch oder lade sie von einer URL.

Fachinformatiker.de, 2024 by SE Internet Services

fidelogo_small.png

Schicke uns eine Nachricht!

Fachinformatiker.de ist die größte IT-Community
rund um Ausbildung, Job, Weiterbildung für IT-Fachkräfte.

Fachinformatiker.de App

Download on the App Store
Get it on Google Play

Kontakt

Hier werben?
Oder sende eine E-Mail an

Social media u. feeds

Jobboard für Fachinformatiker und IT-Fachkräfte

×
×
  • Neu erstellen...