Ulfmann Geschrieben 27. April 2010 Geschrieben 27. April 2010 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? Zitieren
bizarrus Geschrieben 27. April 2010 Geschrieben 27. April 2010 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". Zitieren
Ulfmann Geschrieben 27. April 2010 Autor Geschrieben 27. April 2010 Es geht aber um Multithreading. Sicherlich gäbe es andere, bessere Wege, sowas zu realisieren, aber das war nicht meine Frage. Zitieren
flashpixx Geschrieben 27. April 2010 Geschrieben 27. April 2010 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 Zitieren
Ulfmann Geschrieben 27. April 2010 Autor Geschrieben 27. April 2010 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? Zitieren
Ulfmann Geschrieben 27. April 2010 Autor Geschrieben 27. April 2010 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 Zitieren
flashpixx Geschrieben 27. April 2010 Geschrieben 27. April 2010 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 Zitieren
Ulfmann Geschrieben 28. April 2010 Autor Geschrieben 28. April 2010 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! Zitieren
flashpixx Geschrieben 28. April 2010 Geschrieben 28. April 2010 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 Zitieren
Ulfmann Geschrieben 28. April 2010 Autor Geschrieben 28. April 2010 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. Zitieren
flashpixx Geschrieben 28. April 2010 Geschrieben 28. April 2010 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 Zitieren
Ulfmann Geschrieben 29. April 2010 Autor Geschrieben 29. April 2010 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. Zitieren
flashpixx Geschrieben 29. April 2010 Geschrieben 29. April 2010 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 Zitieren
Ulfmann Geschrieben 29. April 2010 Autor Geschrieben 29. April 2010 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. Zitieren
flashpixx Geschrieben 29. April 2010 Geschrieben 29. April 2010 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. Zitieren
Ulfmann Geschrieben 29. April 2010 Autor Geschrieben 29. April 2010 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. Zitieren
flashpixx Geschrieben 29. April 2010 Geschrieben 29. April 2010 (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 29. April 2010 von flashpixx Zitieren
Ulfmann Geschrieben 29. April 2010 Autor Geschrieben 29. April 2010 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 Zitieren
flashpixx Geschrieben 29. April 2010 Geschrieben 29. April 2010 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. Zitieren
Empfohlene Beiträge
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.