Universität Paderborn Prof. Dr. Stefan Böttcher Kapitel 4: Nebenläufigkeit, Threads und Synchronisation (in Anlehnung an Material von Prof. Dr. Gerd Szwillus) Ich bedanke mich bei meinen Kollegen Prof. Dr. Uwe Kastens und Prof. Dr. Gerd Szwillus für das von ihnen bereitgestellte Material zu dieser Vorlesung Grundlagen der Programmierung 2, SS 2008 1 Universität Paderborn Prof. Dr. Stefan Böttcher Gliederung und Ziele diese Kapitels 1.Parallele Prozesse und Threads Æ Grundbegriffe zu Nebenläufigkeit und Threads verstehen 2.Java-Threads Æ Programmierkonzepte für Threads in Java kennenlernen 3.Synchronisation, gegenseitiger Ausschluss Æ Grundprobleme der Synchronisation verstehen lernen Æ das Konzept des Kritischen Abschnitts verstehen Æ elementare Synchronisationskonzepte anwenden lernen 4.Monitorkonzept und Bedingungssynchronisation Æ Monitore als das Synchronisationskonzept anwenden lernen Æ Techniken der Bedingungssynchronisation kennen lernen Æ Betriebsmittelvergabe durch Bedingungssynchronisation lösen lernen Grundlagen der Programmierung 2, SS 2008 2 Universität Paderborn Prof. Dr. Stefan Böttcher Was sind parallele Prozesse? Prozess: ist Æ Ausführung eines sequentiellen Programmstückes hat Æ einen ihm zugeordneten Speicher (Adressraum). veränderlich: Speicherinhalt und Programmposition. ein Speicherbereich kann mehreren Prozessen zugeordnet sein Parallele Ausführung von Prozessen: mehrere Prozesse werden gleichzeitig auf mehreren Prozessoren ausgeführt p1: p2: p3: Zeit Grundlagen der Programmierung 2, SS 2008 3 Universität Paderborn Prof. Dr. Stefan Böttcher Nebenläufige Prozesse und Threads Verzahnte Ausführung von Prozessen: mehrere Prozesse werden exklusiv (nicht gleichzeitig) ausgeführt Æ häufiger Wechsel des ausgeführten Prozesses, vermittelt die Illusion, dass alle Prozesse gleichzeitig fortschreiten: p1: p2: p3: Zeit möglich, wenn sich mehrere Prozesse einen Prozessor teilen müssen. Nebenläufige Prozesse: Prozesse, die parallel oder verzahnt ausgeführt werden können. Threads: Prozesse, die parallel oder verzahnt in gemeinsamem Speicher ablaufen Æ Prozessumschaltung ist besonders einfach und schnell (Æ Tafel). Grundlagen der Programmierung 2, SS 2008 4 Universität Paderborn Prof. Dr. Stefan Böttcher Anwendungen nebenläufiger Prozesse Benutzungsoberflächen: Ereignisse werden von einem speziellen Systemprozess weitergegeben Aufwendige Berechnungen nebenläufig Æ Oberfläche nicht blockiert Simulation realer Abläufe: z. B. Produktion in einer Fabrik Animation: Veranschaulichung von Abläufen, Algorithmen; Spiele (Billiard) Steuerung von Geräten: Prozesse im Rechner steuern mehrere gleichzeitig aktive externe Geräte Leistungssteigerung durch Parallelrechner: mehrere Prozesse bearbeiten gemeinsam die gleiche Aufgabe, z. B. paralleles Sortieren großer Datenmengen. Grundlagen der Programmierung 2, SS 2008 5 Universität Paderborn Prof. Dr. Stefan Böttcher Threads in Java Java-Threads: Prozesse, die nebenläufig, gemeinsam im Speicher des Programms ablaufen. Java-Programm p erzeugt neuen Thread t Æ t läuft nebenläufig zu p und im selben Speicherbereich wie p Zwei Techniken zur Thread-Erzeugung in Java: 1. Erben von der Thread-Klasse 2. Implementierung des Interface Runnable Grundlagen der Programmierung 2, SS 2008 6 Universität Paderborn Prof. Dr. Stefan Böttcher Threads in Java: 1. Technik (1) Erben von der Thread-Klasse a) Definiere Klasse T als Unterklasse von Thread, die Methode run() überschreibt class T extends Thread { ... public void run () { //überschreibt die Thread-Methode run() ... } //das als Prozess auszuführende Programmstück T (...) {...} // Konstruktor } b) lege Objekt t der Klasse T an (es ist auch ein Thread-Objekt): Thread t = new T (...); c) starte Thread t mit Aufruf der geerbten Methode t.start(); t.start(); // der neue Prozess beginnt neben dem hier aktiven zu laufen Grundlagen der Programmierung 2, SS 2008 7 Universität Paderborn Prof. Dr. Stefan Böttcher Threads in Java: 1. Technik (2) Beispiel Einfaches Beispiel: class T extends Thread { private String name; private int anzahl; T(String n, int a) { name = n; anzahl = a; } // Konstruktor public void run() { // wird aufgerufen, wenn Thread gestartet wird System.out.print("Thread "+name+" fängt an / "); for (int i=0; i<anzahl; i++) // der Thread scheibt „anzahl“ mal seinen Namen System.out.print(name); System.out.print("Thread "+name+" hört auf / "); } } Grundlagen der Programmierung 2, SS 2008 8 Universität Paderborn Prof. Dr. Stefan Böttcher Threads in Java: 1. Technik (3) Beispiel Beispiel fortgesetzt: die main-Funktion startet 4 Threads, die jeweils sooft wie angegeben ihren Namen ausgeben. class T extends Thread { ... } // wie vorher // Im Hauptprogramm werden vier T-Objekte direkt als Thread eingesetzt: class TTest2 { public static void main (String [] args) { Thread t1 = new T("1", 10); Thread t2 = new T("2", 2); Thread t3 = new T("3", 4); Thread t4 = new T("4", 1); t1.start(); t2.start(); t3.start(); t4.start(); } } Grundlagen der Programmierung 2, SS 2008 TTest2.java 9 Universität Paderborn Prof. Dr. Stefan Böttcher Threads in Java: 2. Technik (1) Überblick a) Eine Benutzerklasse implementiert das Interface Runnable: class Aufgabe implements Runnable { ... public void run () { // vom Interface geforderte Methode run ...// das als Prozess auszuführende Programmstück } Aufgabe (...) {...} // Konstruktor } b) Der Prozess wird als Objekt der vordefinierten Klasse Thread erzeugt, ihm ein Objekt der Benutzerklasse übergeben wird: Thread auftrag = new Thread (new Aufgabe (...)); c) Erst Aufruf Thread-Methode start startet dann den Prozess: auftrag.start(); // Der neue Prozess beginnt, // neben bereits aktiven Prozessen zu laufen. Grundlagen der Programmierung 2, SS 2008 10 Universität Paderborn Prof. Dr. Stefan Böttcher Threads in Java: 2. Technik (2) Beispiel class T implements Runnable { // so könnte T Unterklasse von anderen private String name; // Klassen als Thread sein private int anzahl; public void run() { System.out.print("Thread "+name+" fängt an / "); for (int i=0; i<anzahl; i++) System.out.print(name); System.out.print("Thread "+name+" hört auf / "); } T(String n, int a) { name = n; anzahl = a; } } TTest1.java Grundlagen der Programmierung 2, SS 2008 11 Universität Paderborn Prof. Dr. Stefan Böttcher Threads in Java: 2. Technik (3) Beispiel Im Hauptprogramm werden vier Threads aus den „Runnable“-Objekten vom Typ T mit verschiedenen Parametern erzeugt und gestartet: class TTest1 { public static void main (String [] args) { Thread t1 = new Thread(new T("1", 10)); Thread t2 = new Thread(new T("2", 2)); Thread t3 = new Thread(new T("3", 4)); Thread t4 = new Thread(new T("4", 1)); t1.start(); t2.start(); t3.start(); t4.start(); } } TTest1.java Grundlagen der Programmierung 2, SS 2008 12 Universität Paderborn Prof. Dr. Stefan Böttcher Unterschiede beider Techniken class T implements Runnable { ... public void run() { // Prozess } ... } class T extends Thread { ... public void run() { // Prozess } ... } T kann Unterklasse einer anderen Klasse sein T kann nicht Unterklasse einer anderen Klasse als Thread sein Innerhalb der Klasse T stehen die Methoden von Thread nicht unmittelbar zur Verfügung Innerhalb der Klasse T stehen die Methoden von Thread zur Verfügung Thread t = new Thread(new T(...)); Thread t = new T(...); t.start(); t.start(); Grundlagen der Programmierung 2, SS 2008 13 Universität Paderborn Prof. Dr. Stefan Böttcher Gemeinsamkeiten beider Techniken Klasse Thread verwaltet • die Ausführung der run-Methode im Zusammenspiel mit allen anderen Prozessen • Methoden zum Ändern des dynamischen Zustands des Prozesses • Prioritäten, gegenseitigen Ausschluss, Signalisieren von Bedingungen, Exceptions Grundlagen der Programmierung 2, SS 2008 14 Universität Paderborn Prof. Dr. Stefan Böttcher Ausführungszustände von Threads new: runnable: waiting: timed waiting: terminated: new Prozess ist noch nicht gestartet Prozess läuft Prozess wartet auf einen anderen Prozess (Bedingung) Prozess wartet für eine bestimmte Zeit Prozess ist abgeschlossen // Programm // startet Thread t1.start( ) ; runnable Thread ist fertig terminated warten auf B wait(B) ; waiting schlafen legen sleep(500) ; timed out weiter timed waiting Grundlagen der Programmierung 2, SS 2008 15 Universität Paderborn Prof. Dr. Stefan Böttcher Wichtige Methoden der Klasse Thread public void run () wird überschrieben mit der Methode, die die auszuführenden Anweisungen enthält public void start () startet die Ausführung des Prozesses, nur einmal pro Thread möglich! public void join () throws InterruptedException der aufrufende Prozess wartet bis der angegebene Prozess terminiert ist: try { auftrag.join(); } catch (InterruptedException e) { } public static void sleep (long millisec) throws InterruptedException aufrufender Prozess wartet mindestens die in Millisekunden angegebene Zeit: try { Thread.sleep (1000); } catch (InterruptedException e) { } Grundlagen der Programmierung 2, SS 2008 16 Universität Paderborn Prof. Dr. Stefan Böttcher Threads warten auf andere – join-Bsp.(1) Übergabe eines anderen Threads, auf den mit dem Abschluss ggf. (!) gewartet wird class T extends Thread { private String name; private int anzahl, sleepTime; private Thread wartetAuf; T( ) { … } // übernimmt name, anzahl, sleepTime, wartetAuf public void run() { System.out.println(" Thread "+name+" faengt an / "); for (int i=0; i<anzahl; i++) { try { sleep(sleepTime); } catch(InterruptedException ie) { } System.out.print(name); } if (!(wartetAuf == null)) try { wartetAuf.join(); } catch(InterruptedException ie) { } System.out.println(" Thread "+name+" endet / "); } } Grundlagen der Programmierung 2, SS 2008 17 Universität Paderborn Prof. Dr. Stefan Böttcher Threads warten auf andere – join-Bsp.(2) Übergabe eines anderen Threads, auf den mit dem Abschluss ggf. (!) gewartet wird z.B. Starten von vier Threads: class TTest3 { public static void main (String [] args) { Thread t1 = new T("1", 10, 1000, null); Thread t2 = new T("2", 10, 4000, t1); Thread t3 = new T("3", 20, 600, t2); Thread t4 = new T("4", 25, 500, t3); } } // wartet auf niemanden // wartet auf t1 // wartet auf t2 // wartet auf t3 TTest3.java Grundlagen der Programmierung 2, SS 2008 18 Universität Paderborn Prof. Dr. Stefan Böttcher Synchronisation – Einführung (1) Wenn mehrere Prozesse die Werte gemeinsamer Variablen verändern, kann eine ungünstige Verzahnung (Parallelausführung) zu inkonsistenten Daten führen. Beispiel (Geburtstag): - zwei Prozesse p (Tante) und q (Oma) überweisen zum Geburtstag - benutzen gemeinsame Variable konto und je eine lokale Variable: Prozess p: tmp = konto; konto = tmp + 300; Prozess q: tmq = konto; konto = tmq + 1000; p holt sich Kontostand 400 €, q auch, q schreibt 1400 €, p überschreibt mit 700 € 1000 € futsch ... ! Was könnte man tun? Was für ein Konzept hätte man gerne Æ überlegen Sie ! Grundlagen der Programmierung 2, SS 2008 19 Universität Paderborn Prof. Dr. Stefan Böttcher Synchronisation – Einführung (2) Experiment: Ist es wirklich so schlimm? SynchTest.java Zwei Prozesse, jeder führt 5 Buchungen durch, laufen nebenläufig ohne Kontrolle Jeder Prozess hat eine individuelle Bearbeitungsdauer und Wartezeit zwischen je zwei Buchungen class Ablauf extends Thread { private int tmp; private int betrag; private int zeitBedarf, zeitAbstand; private String name; Ablauf (String name, int zeitBedarf, int zeitAbstand, int betrag) { ... } public void run() { … } } Grundlagen der Programmierung 2, SS 2008 20 Universität Paderborn Prof. Dr. Stefan Böttcher Synchronisation – Einführung (3) class Ablauf extends Thread { // … public void run() { for (int i=0; i<5; i++) { // 5 Buchungen werden durchgeführt // Warten auf die nächste Buchung try { sleep(zeitAbstand); } catch(InterruptedException ie) { }; tmp = konto; // Durchführung der Buchung System.out.println(name+" holt sich Kontostand "+tmp); // sleep simuliert die Dauer der Berechnung try { sleep(zeitBedarf); } catch( InterruptedException ie ) { } ; konto = tmp + betrag; System.out.println(name+" setzt Kontostand auf "+(tmp+betrag)); } } } Grundlagen der Programmierung 2, SS 2008 21 Universität Paderborn Prof. Dr. Stefan Böttcher Synchronisation – Einführung (4) Anlegen zweier Buchungsprozesse A und B: private Ablauf tante = new Ablauf("Prozess A", 500, 300, 300); private Ablauf oma = new Ablauf("Prozess B", 200, 400, 1000); Prozess A holt sich Kontostand 400 Prozess B holt sich Kontostand 400 … Kontostand am Ende: 2500 A bucht alle 300 ms und braucht dafür 500ms B bucht alle 400 ms und braucht dafür 200ms Typisches Ergebnis Æ unrealistisch? Langsame Ausführung, kurze Abstände...! Fehler werden seltener, treten aber doch immer wieder auf! Also: was können wir tun? Grundlagen der Programmierung 2, SS 2008 22 Universität Paderborn Prof. Dr. Stefan Böttcher Kritischer Abschnitt Kritischer Abschnitt eines Prozesses: eine oder mehrere zusammengehörige Operationen, in denen ein Prozess Variablen liest oder ändert, die von mehreren Prozessen gemeinsam benutzt werden Synchronisationsregel: Prozesse müssen kritische Abschnitte unter gegenseitigem Ausschluß ("mutual exclusion") ausführen: d.h., zu jedem Zeitpunkt darf sich höchstens ein Prozess in seinem kritischen Abschnitt befinden. Andere Prozesse, die für die gleichen Variablen mit der Ausführung eines kritischen Abschnitts beginnen wollen, müssen warten. Grundlagen der Programmierung 2, SS 2008 23 Universität Paderborn Prof. Dr. Stefan Böttcher Monitorkonzept Monitor : Ein Modul, der Daten und die Operationen darauf kapselt. Die kritischen Abschnitte von Operationen auf den Daten werden als Monitor-Operationen formuliert. Prozesse rufen Monitor-Operationen auf, um auf die Daten zuzugreifen. Monitor-Operationen werden unter gegenseitigem Ausschluss ausgeführt. Grundlagen der Programmierung 2, SS 2008 24 Universität Paderborn Prof. Dr. Stefan Böttcher Monitore in Java Anweisungsblöcke oder Methoden einer Klasse, die kritische Abschnitte auf Objektvariablen implementieren, können als synchronized gekennzeichnet werden: class Konto { synchronized public void kontoBewegung (...) {...} ... private int stand ; // kontostand } Jedes Objekt der Klasse wirkt als Monitor für seine (!) Objektvariablen: Aufrufe von synchronized-Methoden von mehreren Prozessen für dasselbe Objekt werden unter gegenseitigem Ausschluss ausgeführt. Zu jedem Zeitpunkt kann pro Bank-Objekt (!) höchstens 1 Prozess mit synchronized Methoden auf Objektvariablen zugreifen. Grundlagen der Programmierung 2, SS 2008 25 Universität Paderborn Prof. Dr. Stefan Böttcher Monitore in Java – Konto-Beispiel (1) Nochmal unser Experiment (erst ohne Monitor, dann mit Monitor): class Konto { // Eine Klasse für die Konten – jedes Konto ein Objekt private int stand = 0; public void bewegung(String name, int betrag) // Kritischer Abschnitt { int tmp; tmp = stand; System.out.println(name+": Gelesener Kontostand "+ tmp); tmp = tmp + betrag; System.out.println(name+": Geschriebener Kontostand "+ tmp); stand = tmp; } } Ablauf-Klasse beschreibt Prozess, der fünfmal das Gleiche bucht Im Hauptprogramm wird ein Konto angelegt und zwei Abläufe werden darauf losgelassen - Übergabe von Bearbeitungszeit und Wartezeit OhneMonitorTest.java Grundlagen der Programmierung 2, SS 2008 26 Universität Paderborn Prof. Dr. Stefan Böttcher Monitore in Java – Konto-Beispiel (2) (mit Monitor): class Konto { // Eine Klasse für die Konten – jedes Konto ein Objekt … // wie vorher synchronized public void bewegung(String name, int betrag) // Monitor { // … wie vorher } } Änderung: Schlüsselwort synchronized bei bewegung(...): synchronized public void bewegung(String name, int betrag) {...} Methode kann nun pro Konto-Objekt von maximal einem Prozess zur Zeit ausgeführt werden! MonitorTest.java Korrekter Ablauf, unabhängig von den Zeitangaben! Grundlagen der Programmierung 2, SS 2008 27 Universität Paderborn Prof. Dr. Stefan Böttcher Monitore in Java – Konto-Beispiel (3) Eigenschaft aller Beispiele zu Monitoren: Operationen im kritischen Abschnitt (hier: Zugriff auf Bewegung eines Kontos) nur im wechselseitigen Ausschluss Eigenschaft dieses Beispiels: sobald die "Ressource" Konto frei ist, kann jedes der beiden Ablauf-Objekte diese nutzen, es gibt keine weitere Vorbedingung! Daher reicht die Sicherstellung, dass kein doppelter Zugriff im kritischen Abschnitt passiert. Das reicht aber nicht immer Æ folgende Beispiele Grundlagen der Programmierung 2, SS 2008 28 Universität Paderborn Prof. Dr. Stefan Böttcher Bedingungssynchronisation Bedingungssynchronisation: Ein Prozess wartet, bis eine Bedingung erfüllt ist – verursacht durch einen anderen Prozess; z.B. erst dann in einen Puffer schreiben, wenn darin Platz ist. Bedingungssynchronisation im Monitor: Ein Prozess, der in einer kritischen Monitor-Operation auf eine Bedingung wartet, muss den Monitor freigeben, damit andere Prozesse den Zustand des Monitors ändern können. Grundlagen der Programmierung 2, SS 2008 29 Universität Paderborn Prof. Dr. Stefan Böttcher Bedingungssynchronisation in Java mit vordefinierten Methoden der Klasse Object (müssen aus synchronized Abschnitten heraus aufgerufen werden) wait( ) blockiert den aufrufenden Prozess und gibt den Monitor frei – das Objekt, dessen synchronized Methode er gerade ausführt notify( ) weckt einen der in diesem Monitor blockierten Prozesse; welcher ist nicht definiert notifyAll( ) weckt alle in diesem Monitor blockierten Prozesse; sie können weiterlaufen, sobald der Monitor frei ist Grundlagen der Programmierung 2, SS 2008 30 Programmierschema zur Bedingungssynchronisation in Java Universität Paderborn Prof. Dr. Stefan Böttcher a) Anforderung Solange meine Anforderung nicht erfüllt: wait(); // Hier geht’s nur weiter, wenn andere mich "benachrichtigen" Wenn Anforderung jetzt erfüllt: Ressourcen nutzen. Sonst: wieder warten! b) Rückgabe Ressourcen zurückgeben Allen Wartenden sagen, dass sie es jetzt wieder probieren können: notifyAll(); Wichtig bei a): Nachdem ein blockierter Prozess geweckt wurde, muss er die Bedingung, auf die er wartet, erneut prüfen – sie könnte schon durch schnellere Prozesse wieder ungültig sein: while (!erfüllt) try { wait( ); } catch ( InterruptedException e ) { } Grundlagen der Programmierung 2, SS 2008 31 Beispiel: Betriebsmittelvergabe (1) Universität Paderborn Prof. Dr. Stefan Böttcher Monitor zur Vergabe von Ressourcen („Betriebsmitteln“): Aufgabe: - Monitor verwaltet eine begrenzte Anzahl gleichartiger Ressourcen - Prozesse fordern einige Ressourcen an und geben sie später zurück Programmstruktur: Objekt einer Klasse Monitor Æ verwaltet die verfügbaren Ressourcen (zählen genügt) Eine Klasse Verbraucher (Unterklasse von Thread), Æ Verbraucherprozess - mehrfach instanziiert; alle Verbraucher „streiten“ sich um die Ressourcen ein Hauptprogramm Æ legt fest, wie viele Exemplare der Ressource es gibt Æ startet die Verbraucher. Grundlagen der Programmierung 2, SS 2008 32 Universität Paderborn Prof. Dr. Stefan Böttcher Beispiel: Betriebsmittelvergabe (2) Der Monitor: class Monitor { // Anzahl der verfügbaren Ressourcen, diese Variable private int vorhanden; // wird überwacht! Monitor(int v) { vorhanden = v; } // Initialisierung mit der Maximalzahl synchronized void anfordern(int n, int wer) { ... } // Mit dieser Anfrage an den Monitor fordert Verbraucher “wer” // insgesamt n Elemente der Ressource an synchronized void freigeben(int n, int wer) { ... } // Mit dieser Anfrage gibt der Verbraucher “wer” die von ihm // gebrauchten insgesamt n Elemente der Ressourcen zurück } Grundlagen der Programmierung 2, SS 2008 33 Universität Paderborn Prof. Dr. Stefan Böttcher Beispiel: Betriebsmittelvergabe (2) class Monitor { … // Verbraucher Nummer “wer” fordert “n” Ressourcen an synchronized void anfordern (int n, int wer) { // solange nicht genug verfügbar sind... while (vorhanden < n) { // wartet der Prozess bis er “geweckt” wird try { wait(); } catch(InterruptedException e) {} } // jetzt while-Schleife zuende Æ genug Ressourcen vorhanden! vorhanden -= n; // Her damit! } // Die Rückgabe: Verbraucher “wer” gibt “n” Ressourcen zurück synchronized void freigeben (int n, int wer) { vorhanden += n; // Die eigentliche Rückgabe notifyAll(); // Alle wartenden Prozesse werden geweckt } } Grundlagen der Programmierung 2, SS 2008 34 Universität Paderborn Prof. Dr. Stefan Böttcher Beispiel: Betriebsmittelvergabe (3) Der Verbraucherprozeß: class Verbraucher extends Thread { private Monitor mon; // merkt sich, welcher Monitor ihn bedient private int ident, // Identifikation des Prozesses (0, 1, 2, ...) wieOft, // Wie oft fordert der Prozess Ressourcen an? max; // Wie viele Ressourcen werden maximal angefordert? Verbraucher(...) {...} // Der übliche Konstruktor public void run() { while (wieOft > 0) // Iteration über die Anforderungen { // Wieviel Ressourcen werden angefordert (zwischen 1 und max) int anforderung = (int)( Math.random()*(max-1) ) + 1; mon.anfordern(anforderung, ident); // Die Anforderung // Verwendung der Ressource für 1...1000 ms (Zufall) try { sleep( (int)( Math.random()*999 ) + 1); } catch(InterruptedException e) {} mon.freigeben(anforderung, ident); // Die Freigabe wieOft--; } } } Grundlagen der Programmierung 2, SS 2008 35 Universität Paderborn Prof. Dr. Stefan Böttcher Beispiel: Betriebsmittelvergabe (4) Das Hauptprogramm (MonitorMitRessourcen.java): class MonitorMitRessourcen { public static void main(String [] args) { int vorhanden = 20; // Maximal sind 20 Ressourcen verfügbar Monitor mon = new Monitor (vorhanden); // Starte 5 neue Verbraucher, die je 4 Anforderungen erzeugen for (int i=0; i < 5; i++) new Verbraucher(mon, i, 4, vorhanden).start(); // jeder Verbraucher darf maximal 20 ("vorhanden" viele) // Ressourcen anfordern } } Grundlagen der Programmierung 2, SS 2008 36 Universität Paderborn Prof. Dr. Stefan Böttcher Beispiel: Betriebsmittelvergabe (5) Ein typischer Ablauf: Verbraucher 0 fordert 15 Elemente an. Verbraucher 0 bekommt 15 Elemente. Jetzt verfügbar: 5 Verbraucher 1 fordert 5 Elemente an. Verbraucher 1 bekommt 5 Elemente. Jetzt verfügbar: 0 Verbraucher 2 fordert 19 Elemente an. Verbraucher 3 fordert 9 Elemente an. Verbraucher 4 fordert 13 Elemente an. Verbraucher 1 gibt 5 Elemente frei. Jetzt verfügbar: 5 Verbraucher 1 fordert 17 Elemente an. Verbraucher 0 gibt 15 Elemente frei. Jetzt verfügbar: 20 Verbraucher 0 fordert 19 Elemente an. Verbraucher 0 bekommt 19 Elemente. Jetzt verfügbar: 1 Verbraucher 0 gibt 19 Elemente frei. Jetzt verfügbar: 20 Verbraucher 0 fordert 2 Elemente an. ... Grundlagen der Programmierung 2, SS 2008 37 Universität Paderborn Prof. Dr. Stefan Böttcher Betriebsmittelvergabe (6) Zusammenfassung Schema: Monitor verwaltet endliche Menge von k ≥ 1 gleichartigen Ressourcen. Prozesse fordern jeweils unterschiedlich viele (n) Ressourcen an, 1 ≤ n ≤ k, und geben sie nach Gebrauch wieder zurück. Wartebedingung für Anfordern ist „Sind n Ressourcen verfügbar?“ Zu jedem Zeitpunkt zwischen Aufrufen der Monitor-Methoden gilt: „Die Summe der freien und der vergebenen Ressourcen ist k.“ Beispiele: • Walkman-Vergabe an Besuchergruppen im Museum • auch abstrakte Ressourcen, z. B. das Recht, eine Brücke begrenzter Kapazität (Anzahl Fahrzeuge oder Gesamtgewicht) zu befahren. • auch gleichartige Ressourcen mit Identität, z.B. Nummern der vom Taxi-Unternehmen vergebenen Taxis (Der Monitor muss dann die Identitäten der Ressourcen verwalten.) Grundlagen der Programmierung 2, SS 2008 38 Universität Paderborn Prof. Dr. Stefan Böttcher Beschränkter Puffer (1) - Problemstellung Schema: Beschränkter Puffer Ein Monitor speichert Elemente in einem Puffer von beschränkter Größe. Produzenten-Prozesse liefern einzelne Elemente, Konsumenten-Prozesse entnehmen einzelne Elemente. Der Monitor stellt sicher, dass die Elemente in der gleichen Reihenfolge geliefert und entnommen werden. (Datenstruktur: Schlange, Queue) Die Wartebedingungen lauten: - in den Puffer schreiben, nur wenn er nicht voll ist, - aus dem Puffer nehmen, nur wenn er nicht leer ist. Grundlagen der Programmierung 2, SS 2008 39 Universität Paderborn Prof. Dr. Stefan Böttcher Beschränkter Puffer (2) – Struktur der Lösung Was brauchen wir? 1. Ein Hauptprogramm: startet Produzenten und Verbraucher 2. Eine Klasse (Buffer) für das Monitorobjekt Æ verwaltet die verfügbaren Ressourcen in einer Warteschlange 3. Eine Klasse Produzent (Unterklasse von Thread) - mehrfach instanziiert; alle Produzenten wollen ihre „Produkte“ in die Warteschlange schreiben (Operationen enqueue - einspeichern, dequeue – entfernen) 4. Eine Klasse Verbraucher (Unterklasse von Thread) - mehrfach instanziiert; alle Verbraucher „streiten“ sich um die in der Schlange verwalteten Produkte 5. (hier zufällig:) eigene Hilfsklassen für Queue und Produkt Grundlagen der Programmierung 2, SS 2008 40 Beschränkter Puffer (3) – Details der Lösung das Hauptprogramm Universität Paderborn Prof. Dr. Stefan Böttcher class MonitorMitPuffer { public static void main(String [] args) { Buffer puffer = new Buffer(20); // Lager nimmt max. 20 Autos auf // Die Produzenten und ihre Leistungsfähigkeit: 10 VWs, 3 BMWs, ... new Produzent("VW", 10, puffer).start(); new Produzent("BMW", 3, puffer).start(); new Produzent("Mercedes", 2, puffer).start(); new Produzent("Trabant", 18, puffer).start(); // Die Konsumenten und ihre Gier: 3 Autos für Jan, 10 für Tom, ... new Konsument(„Jan", 3, puffer).start(); new Konsument("Tom", 10, puffer).start(); new Konsument("Gero", 5, puffer).start(); new Konsument("Gunnar", 4, puffer).start(); new Konsument("Gerd", 8, puffer).start(); // Es werden 33 Autos hergestellt und 30 abgenommen } } Grundlagen der Programmierung 2, SS 2008 MonitorMitPuffer.java 41 Beschränkter Puffer (4) – Details der Lösung Monitorklasse für beschränkten Puffer class Buffer { private Queue buf; Universität Paderborn Prof. Dr. Stefan Böttcher // Schlange der Länge n zur Aufnahme der Elemente public Buffer (int n) { buf = new Queue(n); } // Die Größe n der Schlange modelliert die Begrenztheit des Puffers synchronized public void put (Object elem) { ... } // Methode zum Ablegen eines neu produzierten Elementes synchronized public Object get () { ... } // Methode zum Abholen (Verbrauchen) eines Elementes } Grundlagen der Programmierung 2, SS 2008 42 Universität Paderborn Beschränkter Puffer (5) – Details der Lösung Prof. Dr. Stefan Böttcher Monitorklasse für beschränkten Puffer - Details private Queue buf; // Schlange der Länge n zur Aufnahme der Elemente synchronized public void put (Object elem) { // ein Produzenten-Prozess versucht, ein Element zu liefern while (buf.isFull()) { // warten solange der Puffer voll ist try { wait(); } catch (InterruptedException e) {} // Jetzt ist es soweit: Puffer ist nicht mehr voll buf.enqueue (elem); notifyAll(); // jeder blockierte Prozess wird geweckt, prüft seine Wartebedingung } synchronized public Object get () { // ein Konsumenten-Prozess versucht, ein Element zu nehmen while (buf.isEmpty()) // warten solange der Puffer leer ist try { wait(); } catch (InterruptedException e) {} // Jetzt ist es soweit: Es gibt ein Element - das erste kann “geliefert” werden Object elem = buf.first(); buf.dequeue(); notifyAll(); // jeder blockierte Prozess prüft seine Wartebedingung return elem; } Grundlagen der Programmierung 2, SS 2008 43 Beschränkter Puffer (6) – Details der Lösung die Produzent-Klasse Universität Paderborn Prof. Dr. Stefan Böttcher class Produzent extends Thread { String bezeichnung; int produktionsZahl; Buffer buf; Produzent(String ib, int wieviele, Buffer ibuf) { // Konstruktor, Daten übernehmen bezeichnung = ib; produktionsZahl = wieviele; buf = ibuf; } public void run() { // Der Produktionsprozess while (produktionsZahl > 0) { Produkt p = new Produkt("Auto"); // Erzeugen eines neuen Autos System.out.println(bezeichnung+" hat "+p+" produziert"); // Der Produzent möchte es in die Schlange einreihen buf.put(p); System.out.println(bezeichnung+" hat "+p+" abgelegt"); try { sleep( (int)( Math.random()*500 ) + 200 ); } // Ausruhen... (200 bis 700 ms) catch (InterruptedException e) {} produktionsZahl--; } } } Grundlagen der Programmierung 2, SS 2008 44 Beschränkter Puffer (7) – Details der Lösung die Konsument-Klasse Universität Paderborn Prof. Dr. Stefan Böttcher class Konsument extends Thread { String bezeichnung; int verbrauchsZahl; Buffer buf; Konsument(String ib, int wieviele, Buffer ibuf) { //Konstruktor: Werte übernehmen bezeichnung = ib; verbrauchsZahl = wieviele; buf = ibuf; } // Der Konsumentenprozess public void run() { while (verbrauchsZahl > 0) { Produkt p = (Produkt)buf.get(); // Anforderung eines Produktes System.out.println(bezeichnung+" entnimmt "+p); try { sleep( (int)( Math.random()*1500 ) + 500 ); } // Verbrauchen (500 ... 2000 ms) catch (InterruptedException e) {} verbrauchsZahl--; } } } Grundlagen der Programmierung 2, SS 2008 45 Beschränkter Puffer (8) – Details der Lösung Hilfsklassen Queue und Produkt Universität Paderborn Prof. Dr. Stefan Böttcher Klasse Queue (Warteschlange) - Hilfsmittel für den Monitor: void enqueue(Object o) void dequeue() Object first() boolean isFull() boolean isEmpty() - neues Objektes hinten einhängen - vorderstes Objekt entfernen - vorderstes Objekt herausgeben - Test, ob Schlange „voll“ ist (begrenzte Kapazität!) - Test, ob die Schlange leer ist: enqueue und dequeue bekommen Ausgabeanweisungen (für Demozwecke) Hilfsklasse Produkt: class Produkt { String bezeichnung; int id; static int anzahl = 0; // Klassenvariable zählt Produkt-Objekte von 1...n durch public String toString() { return bezeichnung+"("+id+")"; } // i-tes heißt “Auto(i)” Produkt(String ib) { bezeichnung = ib; id = ++anzahl; } } Grundlagen der Programmierung 2, SS 2008 46 Universität Paderborn Prof. Dr. Stefan Böttcher Synchronisation mit Monitoren - Zusammenfassung Monitore in Java: Objekte einer Monitor-Klasse alle kritischen Abschnitte durch synchronized-Methoden codiert Æ wechselseitiger Ausschluss Falls Eintritt in den kritischen Abschnitt von Bedingungen abhängt: wenn Bedingung nicht erfüllt ist, wäre Eintritt in kritischen Abschnitt sinnlos Æ wait( ) - zur Freigabe des Monitors und warten auf notify () wenn sich Bedingungen für andere Prozesse geändert haben (könnten) Æ notify( ) bzw. notifyAll( ) , um andere Prozesse zu wecken nach dem Aufwecken muss Bedingung erneut getestet werden Æ while ( ! Bedingung ) try { wait( ) ; } catch ( InterruptedException e ) { } Wichtige Beispiele aus dem Bereich Betriebsmitelverwaltung: 1. eine Schwellwert-Bedingung (z.B. Resourcen-Verleih: vorhanden >0) 2. zwei Schwellwerte: (z.B. begrenzter Puffer: 0 ≤ Füllung und Füllung ≤ max) Grundlagen der Programmierung 2, SS 2008 47