Einführung Willkommen zu Vorlesung + Praktikum Parallele Prozesse Einführung zur Person: •Harald Gläser •Sprechstunde: Dienstag 12:45 C2.07 •[email protected] •Webpage mit Lehrmaterial (bislang unvollständige Version): http://www.informatik.hs-furtwangen.de/~glaeser/ Einführung 2 Leistungen um Modul zu bestehen (6 ECTS): a) Klausur b) Praktikum: I. Alle Aufgaben müssen von jedem selbständig innerhalb der Praktikumsstunden bearbeitet werden (kein “leeres Blatt”, beim 2. Fehlen: ärztliches Attest). II. Alle Aufgaben müssen bestanden werden. III. Abgabe jeweils nächste Woche IV. 4 Zeitstunden pro Praktikumstermin einplanen Im Praktikum besteht Anwesenheitspflicht Heute statt Praktikum Vorlesung in C1.20 - Erst mal „Stoff“ für Praktikum heranschaffen! im email - UFO System anmelden !! Link zu UFO: http://www.hs-furtwangen.de/fachbereiche/in/deutsch/intranet/uf/?tg=0 Spale Einführung Klausurtermin: Mo 13.07.09 Uhrzeit 11:15 Raum: C0.09 Wie schreibe ich eine 1,0 in der Klausur? •zu Hause Vorlesung vorbereiten: •Folien im Netz / Buch von Oechsle vorher ausdrucken – während Vorlesung Notizen in Folien machen •während Vorlesung aufkommende Fragen sofort stellen! •zu Hause Vorlesung nachbereiten: •Alles verstanden ? – Sonst nächste Stunde Fragen stellen. •4 Zeitstunden sind für die Vorlesung insgesamt vorgesehen -> 2,5 Zeitstunden für Vor- und Nachbereitung -> 1,0 in der Klausur steht nichts im Wege ☺ Buch: Parallele und verteilte Anwendung in Java Rainer Oechsle Hanser Verlag puva.fh-trier.de Weitere empfehlenswerte Quellen: Java Tutorial, Firma Sun, http://java.sun.com/docs/books/tutorial/essential/concurrency/ index.html Java ist auch eine Insel, Christian Ullenboom Fortgeschrittene Programmierung mit Java 5, Johannes Nowak Parallelität, Nebenläufigkeit (concurrency) echte Parallelität Pseudoparallelität Was ist Verteilung? Vorgänge laufen auf lose gekoppelten Systemen ab Lose gekoppelte Systeme: mehrere gekoppelte Prozessoren ohne gemeinsamen Hauptspeicher Programme, Prozesse, Threads Prozess = Programm in Ausführung Rechner Prozess Rechner Prozess Prozess Objekt Objekt Objekt Objekt Objekt Objekt Objekt Thread Thread Thread Thread Thread Thread Prozess Betriebssystem Betriebssystem •Programmstart (z.B. java HelloWorld) = neuer Prozess wird gestartet •Prozess besitzt eigenen Adressraum •Jeder Prozess hat mindestens einen Thread •Hintergrund Threads (z.B. garbage collection) •main Thread -> kann neue Threads starten •Threads desselben Prozesses können auf die gleichen Objekte zugreifen •Isolierung der Prozesse durch Systemaufrufe kontrolliert durchbrechbar •Adressraum eines Prozesses kann durch Systemaufruf vergrößert werden oder Nachrichten über das Netz absetzen Inhalt der Vorlesung •Parallelität innerhalb eines Prozesses •Verteilung (Parallelität zwischen Prozessen verschiedener Rechner) •Client-Server Anwendungen •webbasierte Anwendungen •Socket •RMI •Parallelität zwischen Prozessen (desselben Rechners) •Die Konzepte der Verteilung funktionieren auch für Prozesse, die nur auf einem Rechner ablaufen Java Threads Ableiten von Klasse Thread ODER (wg. fehlender Mehrfachvererbung) Ableiten von Interface Runnable Ableiten von Klasse Thread run: run:Was Wasim imThread Threadausgeführt ausgeführtwird wird class class MyThread MyThread extends extends Thread{ Thread{ public void run() { public void run() { System.out.println(„Hello“); System.out.println(„Hello“); }} public public static static void void main(String[] main(String[] args) args) {{ MyThread MyThread tt == new new MyThread(); MyThread(); t.start(); t.start(); }} }} start: start:startet starteteinen einenneuen, neuen,parallelen parallelenThread Thread •pro Thread Objekt darf start nur einmal aufgerufen werden •Thread Objekt existiert unabhängig von Thread (also auch bevor run ausgeführt wird bzw. nach Ende von run) Was ist der Unterschied, wenn man statt start direkt run aufruft ? direkter Aufruf von run main run start Aufruf main run Implementieren der Schnittstelle Runnable public public class class SomethingToRun SomethingToRun implements implements Runnable Runnable {{ public run ist durch Runnable vorgeschrieben public void void run() run() run ist durch Runnable vorgeschrieben {{ System.out.println("Hello System.out.println("Hello World"); World"); }} public public static static void void main(String[] main(String[] args) args) {{ SomethingToRun SomethingToRun runner runner == new new SomethingToRun(); SomethingToRun(); Thread t = new Thread(runner); Thread t = new Thread(runner); t.start(); t.start(); }} }} Thread Threaderzeugen erzeugendurch durchKonstruktor KonstruktorAufruf Aufruf Beispiel public public class class Loop3 Loop3 extends extends Thread Thread {{ public public Loop3(String Loop3(String name) name) {{ super(name); super(name); }} public public void void run(){ run(){ for(int i for(int i == 1; 1; ii <= <= 100; 100; i++){ i++){ System.out.println(getName() System.out.println(getName() ++ "" (" (" ++ ii ++ ")"); ")"); try{ try{ sleep(100); sleep(100); }catch(InterruptedException }catch(InterruptedException e){} e){} }} }} }} public public static static void void main(String[] main(String[] args) args) {{ Loop3 Loop3 t1 t1 == new new Loop3("Thread Loop3("Thread 1"); 1"); Loop3 Loop3 t2 t2 == new new Loop3("Thread Loop3("Thread 2"); 2"); Loop3 t3 = new Loop3("Thread 3"); Loop3 t3 = new Loop3("Thread 3"); t1.start(); t1.start(); t2.start(); t2.start(); t3.start(); t3.start(); }} Code Ausführung •eventuell wachen mehrere Threads gleichzeitig zu einer quantisierten Zeit auf •Ausführungsreihenfolge kann nicht mit sleep erzwungen werden •Methodenlokale Variablen für jeden Thread vorhanden ! •Die Variablen werden bei einer Threadumschaltung „auf Eis gelegt“ •Wenn der Thread weitergeht, werden die Variablen weiterbenutzt Probleme beim Zugriff auf gemeinsam genutzte Objekte Andrea Müller (Thread) Petra Schmitt (Thread) Bank Konto 0 47 public public void void transferMoney(int transferMoney(int accountNumber, accountNumber, float float amount) amount) {{ float float oldAccount oldAccount == account[accountNumber]; account[accountNumber]; account[accountNumber] account[accountNumber] == oldAccount oldAccount ++ amount; amount; }} Probleme beim Zugriff auf gemeinsam genutzte Objekte Andrea Müller (Thread) aktiv Petra Schmitt (Thread) Bank Konto 0 47 -100.0 public accountNumber, float amount) {{ public void void transferMoney(int transferMoney(int accountNumber, float amount) 0 float float oldAccount oldAccount == account[accountNumber]; account[accountNumber]; STOP }} account[accountNumber] account[accountNumber] == oldAccount oldAccount ++ amount; amount; Probleme beim Zugriff auf gemeinsam genutzte Objekte Andrea Müller (Thread) aktiv Petra Schmitt (Thread) Bank Konten 1000 47 -100.0 +1000.0 public transferMoney(int accountNumber, float amount) {{ public void void transferMoney(int accountNumber, float amount) 0 0 float float oldAccount oldAccount == account[accountNumber]; account[accountNumber]; +1000.0 0 +1000.0 account[accountNumber] account[accountNumber] == oldAccount oldAccount ++ amount; amount; }} STOP Probleme beim Zugriff auf gemeinsam genutzte Objekte Andrea Müller (Thread) aktiv Petra Schmitt (Thread) Bank Konto -100 47 -100.0 public transferMoney(int accountNumber, float amount) {{ public void void transferMoney(int accountNumber, float amount) 0 float float oldAccount oldAccount == account[accountNumber]; account[accountNumber]; -100.0 0 -100.0 account[accountNumber] account[accountNumber] == oldAccount oldAccount ++ amount; amount; }} STOP Warum löst dies nicht das Problem? public public void void transferMoney(int transferMoney(int accountNumber, accountNumber, float float amount) amount) {{ account[accountNumber] account[accountNumber] += += amount; amount; }} Diese Transaktion ist nicht atomar, da im Bytcode aus mehreren Befehlen zusammengesetzt: lade Inhalt von account[accountNumber] in ein Register; addiere auf dieses Register den Inhalt von amount; schreibe Register auf account[accountNumber] zurück; Register übernimmt hier die Rolle von oldAccount! Warum löst dies auch nicht das Problem? class class Bank Bank {{ private private boolean boolean locked locked == false; false; public public void void transferMoney(int transferMoney(int accountNumber, accountNumber, float float amount) amount) {{ while(locked); while(locked); locked locked == true; true; float float oldAccount oldAccount == account[accountNumber]; account[accountNumber]; account[accountNumber] account[accountNumber] == oldAccount oldAccount ++ amount; amount; locked = false; locked = false; }} }} 1. Aktives Warten (Schleife wird durchlaufen) ist schlecht (Polling) 2. Diese Transaktion ist auch nicht atomar: Bytecode: a) lade locked in ein Register; b) falls dieses Register == true, springe zurück zur vorigen Anweisung; c) lade true in locked Problem, wenn nach a) zum anderen Thread geschaltet wird, und der gerade ein true reinschreibt! Synchronized - Methoden public public synchronized synchronized void void transferMoney(int transferMoney(int accountNumber, accountNumber, float float amount) amount) {{ float float oldAccount oldAccount == account[accountNumber]; account[accountNumber]; account[accountNumber] account[accountNumber] == oldAccount oldAccount ++ amount; amount; }} •Java hat Sperr-Mechanismus •Sperre liegt bei Object -> alle Objekte erben Sperre •Aufruf synchronized Methode = Sperre setzen auf zugehörigem Objekt •Wenn Methode abgearbeitet = Sperre frei -> nächster Thread kann aufrufen •Fremde Threads können keine synchronized Methoden dieses Objekts aufrufen •Sie erhalten keine CPU Zeit mehr (kein Polling) •Erster Thread kann alle synchronized Methoden weiterhin benutzen Synchronized - Blöcke •Sperre der ganzen Bank ineffizient: Alle Konten gesperrt. •Lösung: z.B. synchronized Block public public void void transferMoney(int transferMoney(int accountNumber, accountNumber, float float amount) amount) {{ synchronized synchronized (account[accountNumber]) (account[accountNumber]) {{ float float oldAccount oldAccount == account[accountNumber]; account[accountNumber]; account[accountNumber] account[accountNumber] == oldAccount oldAccount ++ amount; amount; }} }} •Objekt hinter synchronized gesperrt •Nur benutztes Konto Objekt gesperrt •synchronized Methode == synchronized(this) Block •über das Block Objekt lassen sich mehrere synchronized Blöcke an unterschiedlichen Stellen im Programm gleichzeitig sperren Wirkung von synchronized class class CC {{ public public void void m1() m1() {...} {...} public void m2() {...} public void m2() {...} public public synchronized synchronized void void ms1() ms1() {...} {...} public synchronized void ms2() {...} public synchronized void ms2() {...} }} C o1 = new C(); C o2 = new C(); Thread B: o1.m1(); o1.m2() o1.ms1(); o1.ms2() o2.m1(); o2.m2() o2.ms1(); o2.ms2() Thread A: o1.ms1() geht geht nicht (o1 gesperrt) geht geht (o2 anderes Objekt) Thread A: o1.m1() geht geht geht geht Thread A ruft zuerst auf, dann versucht B aufzurufen static synchronized Methode: •Sperre gilt für die Klasse bzw. die static synchronized Methoden der Klasse •Objekte der Klasse werden durch static synchronized nicht gesperrt Ein gesperrtes Objekt kann man auch „Monitor“ nennen, nach einem allgemeinen Konzept von Hoare (1974) Klemmt das ? class class MyClass MyClass {{ public public synchronized synchronized void void m1() m1() {{ m2(); m2(); }} public public synchronized synchronized void void m2() m2() {} {} public static void main(String[] public static void main(String[] args) args) {{ MyClass MyClass obj obj == new new MyClass(); MyClass(); obj.m1(); obj.m1(); //wird //wird diese diese Stelle Stelle erreicht?? erreicht?? }} }} Nein, klemmt nicht. Der eigene Thread wird nicht blockiert. Ist Synchronisation von Zugriff auf methodenlokale Variablen nötig? •primitive Datentypen: nein, da für jede Methode eigene Variablen da •Referenz Datentypen: nein, wenn in Methode Objekt erzeugt möglicherweise, wenn auf Attribute verwiesen wird -> könnten von anderen Threads verändert werden Ist Synchronisation von Zugriff auf Attribute mit primitiven Datentyp nötig? •Lesen und Schreiben aller Referenzen und primitiver Datentypen außer double und long in Java atomar ABER: Optimierung vom Compiler im Bytecode möglich: Wert nicht immer vom Hauptspeicher lesen sondern in Register zwischenspeichern -> synchronized verhindert dass! (oder Attribut mit volatile modifizieren) •long, double haben 64bit -> Lesen, Schreiben erfolgt in 32bit Schritten nicht atomar -> synchronized Regel für synchronized Wenn zuerst Zustands ändernde Threads und dann nur noch lesende Threads auf Attribut zugreifen -> kein synchronized (Neustarten von Threads hat volatile Effekt). Wenn mindestens ein Zustands ändernder Thread gleichzeitig mit anderen Threads auf ein Objekt zugreift -> synchronized von allen lesenden und schreibenden Methoden •Schreibende Methoden überführen Objekt von einem konsistenten Zustand in einen anderen in MEHREREN Schritten •Wenn in Zwischenschritt etwas gelesen wird ist das undefiniert •Beispiel: interne Überweisungen in Bank und Kontrollroutine, die prüft, ob Summe der Konten immer gleich ist •Kontrollroutine kann lesen, wenn gerade ein Betrag abgebucht aber noch nicht wieder auf dem anderen Konto gelandet ist -> lesende Methoden müssen auch gesperrt -> FOLGE ?? werden Wie kann man auf das Ende eines Thread warten? MyThread MyThread tt == new new MyThread(); MyThread(); t.start(); t.start(); while while (t.isAlive()); (t.isAlive()); // jetzt // jetzt gilt: gilt: t.isAlive() t.isAlive() == == false; false; also also ist ist der der Thread Thread zuende zuende Was ist daran schlecht ? Polling MyThread MyThread tt == new new MyThread(); MyThread(); t.start(); t.start(); t.join(); t.join(); // // der der Thread Thread tt ist ist zuende zuende Beenden von Threads •Methode stop bricht Thread sofort ab •stop ist deprecated – Warum ? •stop bringt Objekte eventuell vorübergehend in einen inkonsistenten Zustand (Beispiel, das Bankenproblem mit den internen Konten) •nur der Programmierer kann Thread konsistent zum Ende führen •Lösung: interrupt Flag bei dem Thread Objekt benutzen public public void void run() run() {{ int interrupted Flag wird untersucht int ii == 0; 0; interrupted Flag wird untersucht try { try { while(!isInterrupted()) while(!isInterrupted()) {{ i++; i++; System.out.println("Hello System.out.println("Hello World World (" (" ++ ii ++ ")"); ")"); Thread.sleep(3000); Thread.sleep(3000); }} Unterbrechung Unterbrechungauch auchwährend währendsleep, sleep,join, join,wait! wait! }} catch(InterruptedException e){ Dann catch(InterruptedException e){eine Dannerfolgt erfolgt eineInterrupt InterruptException Exception System.out.println(e.getMessage()); System.out.println(e.getMessage()); }} System.out.println("thread System.out.println("thread terminating terminating ..."); ..."); }} catch setzt interrupted Flag wieder auf false catch setzt interrupted Flag wieder auf false }} public public static static void void main(String[] main(String[] args){ args){ StopThreadByInterrupt st = new StopThreadByInterrupt st = new StopThreadByInterrupt(); StopThreadByInterrupt(); try{ try{ Thread.sleep(9100); Thread.sleep(9100); }} catch(InterruptedException catch(InterruptedException e){} e){} st.interrupt(); st.interrupt(); }} Thread Threadsoll sollabgebrochen abgebrochenwerden werden static staticboolean booleaninterrupted(): interrupted():frägt frägtFlag Flagab, ab,und undsetzt setztes esauf auffalse! false! Asynchrone Beauftragung mit befristetem Warten private private static static final final int int TIMEOUT=10000; TIMEOUT=10000; public public static static void void main(String[] main(String[] args) args) {{ Server Server server server == new new Server(...); Server(...); server.start(); server.start(); //auf //auf Serverende Serverende beschränkte beschränkte Zeit Zeit warten warten try try {{ Maximale Wartezeit auf den Thread Maximale Wartezeit auf den Thread server.join(TIMEOUT); server.join(TIMEOUT); }} catch(InterruptedException catch(InterruptedException e) e) {} {} //Server //Server sanft sanft abbrechen abbrechen server.interrupt(); server.interrupt(); //Synchronisation //Synchronisation mit mit Server Server try { try { server.join(); server.join(); }} catch(InterruptedExceptione) catch(InterruptedExceptione) {} {} //Ergebnis //Ergebnis abholen abholen und und ausgeben ausgeben double result = server.getResult(); double result = server.getResult(); System.out.println("Ergebnis:"+result); System.out.println("Ergebnis:"+result); }} Vorläufige Zusammenfassung: •Objekte kapseln Zustände •Für Zustände gelten Konsistenzbedingungen (Invarianten) •Methoden überführen von einem konsistenten Zustand in einen anderen, wobei zwischendurch die Konsistenz evtl. verletzt wird •Synchronized Methoden gewährleisten, dass Threads bei Methodenaufruf konsistenten Zustand vorfinden •Synchronized sperrt Objekte in inkonsistenten Zuständen vor Zugriffen von anderen Threads •Manchmal sollen Objekte noch weiter gesperrt sein, bis weitere anwendungsabhängige Bedingungen erfüllt sind Beispiel Parkhaus •Jedes Auto ein Thread •Ein Parkhausobjekt mit Methoden enter und leave class class ParkingGarage ParkingGarage {{ private private int int places; places; public public ParkingGarage(int ParkingGarage(int places) places) {{ if(places if(places << 0) 0) places places == 0; 0; this.places = places; this.places = places; }} public public synchronized synchronized void void enter() enter() {{ while(places while(places == == 0); 0); places--; places--; }} public public synchronized synchronized void void leave() leave() {{ places++; places++; }} }} Was Wasist isthier hierfalsch falsch?? •Polling •Dauersperre, wenn places == 0 nicht in synchronized Methode warten! private private synchronized synchronized boolean boolean isFull() isFull() {{ return return (places (places == == 0); 0); }} private private synchronized synchronized void void decrement() decrement() {{ places--; places--; }} public public void void enter() enter() {{ while(isFull()); while(isFull()); decrement(); decrement(); }} Was Wasist isthier hierfalsch falsch?? public public synchronized synchronized void void leave() leave() {{ places++; places++; }} Was kann passieren, wenn noch genau ein Platz im Parkhaus frei ist und zwei Autos reinfahren wollen? Zustandsübergangsdiagramm für Threads Lösung: wait und notify class class Object Object {{ …… public public final final void void wait() wait() throws throws InterruptedException InterruptedException {…} {…} public final void notify() {…} public final void notify() {…} }} •beide Methoden dürfen nur auf gesperrten Objekten (in synchronized Methoden) aufgerufen werden sonst IllegalMonitorStateException •wait schiebt Thread in Warteschlange mit Threads und hebt Sperre auf •notify erlaubt irgendeinem (unbestimmbaren) Thread aus der Warteschlange weiterzumachen (da, wo er aufgehört hatte) •ist Warteschlange leer, bewirkt notify nichts Lösung des Parkhausproblems: public public synchronized synchronized void void enter() enter() {{ while(places while(places == == 0) 0) {{ try try {{ wait(); wait(); }} catch catch (InterruptedException (InterruptedException e) e) {{ }} }} places--; places--; }} public public synchronized synchronized void void leave() leave() {{ places++; places++; notify(); notify(); }} Ist das Programm mit folgender Änderung noch korrekt? public public synchronized synchronized void void leave() leave() {{ notify(); notify(); places++; places++; }} Ja, das Programm ist noch korrekt. Der durch notify geweckte Thread sperrt das Objekt wieder, -aber erst sobald er kann, d.h. wenn der „notify“ Thread die Methode verlassen hat Ist das Programm mit folgender Änderung noch korrekt? public public synchronized synchronized void void enter() enter() {{ if if (places (places == == 0) 0) {{ // // if if statt statt while! while! try try {{ wait(); wait(); }} catch catch (InterruptedException (InterruptedException e) e) {{ }} }} places--; places--; }} Nein, das Programm ist nicht korrekt. Der gerade geweckte Thread könnte sofort wieder gestoppt werden, indem auf einen anderen Thread umgeschaltet wird, der gerade enter aufruft – was passiert dann ? Ein entblockierter Thread muss die Bedingung, auf die er wartet, erneut prüfen - sie könnte noch nicht gültig sein oder schon durch schnellere Threads wieder invalidiert worden sein. Unterschiede sleep und wait ? sleep Methoden der Klasse Thread Klassenmethoden Keine Bedingungen für Aufruf wait Methoden der Klasse Object Instanzmethoden Kann nur auf Objekt aufgerufen werden, das gesperrt ist Falls Objekte gesperrt sind, Die Sperre des Objekts, auf bleiben sie gesperrt das wait aufgerufen wird, wird aufgehoben (eventuelle andere Sperren bleiben gesetzt) Das Erzeuger Verbraucher Problem Producer Buffer data available = true/false Consumer private private boolean boolean available available == false; false; private int data; Die Klasse Buffer private int data; public public synchronized synchronized void void put(int put(int x) x) {{ while(available) while(available) {{ try try {{ wait(); wait(); }} catch(InterruptedException catch(InterruptedException e) e) {} {} }} data data == x; x; available •es wird ein int Wert gespeichert available == true; true; notify(); notify(); •available zeigt an, ob ein neuer }} public public synchronized synchronized int int get() get() {{ Wert im Buffer vorliegt while(!available){ while(!available){ try{ try{ wait(); wait(); }} catch(InterruptedException catch(InterruptedException e) e) {} {} }} available available == false; false; notify(); notify(); Das Programm stoppt „willkürlich“! return data; return data; }} Threadzustände im Verlauf der Zeit data Consumer Consumerc1 c1c2 c2 Producer Producerp1 p1 put get ready wait Buffer c1 leer p1 notify() c2 voll Threadumschaltung leer Stillstand Stillstand Würde das Programm mit nur einem Producer und Consumer Objekt funktionieren? Wenn c1 statt c2 p2 geweckt hätte – was wäre dann gewesen? Der falsche Thread wurde geweckt Lösung: notifyAll notifyAll() •weckt alle Threads in Warteschlange des Objekts •Objekt muss gesperrt sein bei Aufruf von notifyAll() •wenn Warteschlange leer ist notifyAll wirkungslos •da alle Threads nach Aufwecken die Wartebedingung (nochmals) überprüfen, kann jeder notify durch notifyAll ersetzt werden (aber nicht umgekehrt) •notifyAll ist weniger effizient als notify Wann notifyAll statt notify? 1. Threads mit unterschiedlichen Wartebedingungen in Warteschlange (z.B. „Buffer ist voll“ und „Buffer ist leer“) -> Gefahr: falscher Thread wird geweckt 2. Durch Änderung Objektzustand können mehrere Threads weiterlaufen (z.B. mehrer Autos an grüner Ampel) Prioritäten von Threads public public class class Thread Thread {{ …… public public final final void void setPriority(int setPriority(int newPriority) newPriority) {…} {…} public final in getPriority() {…} public final in getPriority() {…} …… }} Werte der Priority (je höher desto größere Priorität) (Konstanten in Thread) 1 == MIN_PRIORITY 5 == NORM_PRIORITY 10 == MAX_PRIORITY Priorität entscheidet über die Prozessorzuteilungsstrategie (Scheduling): •Arbeitsdauer eines Thread bis zur nächsten Umschaltung •auf welchen Thread als nächstes umgeschaltet wird •Auswirkung der Priorität auf das Scheduling ist vom Betriebssystem abhängig und nicht vorgeschrieben •z.B. können die Prioritäten 1 und 2 im Betriebssystem die gleiche Priorität bedeuten •z.B. kann die Arbeitsdauer eines Thread bis zur nächsten Umschaltung für alle Prioritäten gleich sein etc. •Ein / Ausgabe Threads sollten hohe Priorität haben •rechenintensive Threads sollten niedrige Priorität haben 1. Korrektheit von Programm darf nicht von Prioritäten z.B. Umschaltverhalten des Systems abhängen (nie Prioritäten für Synchronisation benutzen) 2. Prioritäten können Effizienz eines Systems verbessern Mit yield kann man auf einen anderen Thread umschalten (sollte aber in produktivem Code nicht verwendet werden – warum ?) Daemon- und User Threads •Ein Thread kann als Daemon oder User Thread gesetzt werden •Diese Threads unterscheiden sich nur bezüglich des Endes eines Java Prozesses •Ein Java Prozess ist beendet, wenn alle User Threads beendet sind •Daemon Threads sind dann auch zuende, selbst wenn sie Endlosschleifen sind •Berühmte Daemon Threads sind •Reference Handler – sucht nach nicht mehr referenzierten Objekten zur Speicherfreigabe •Finalizer – ruft die Methode finalize von jedem Objekt auf, dass nicht mehr referenziert wird •Beide Threads machen „garbage collection“ •Jeder Thread (bis auf main Thread) kann in Daemon Thread verwandelt werden, aber nur bevor er gestartet wurde Frage: wenn man Java Prozess abschiesst – laufen Daemons noch weiter public public class class Thread Thread {{ …… public public final final void void setDaemon(boolean setDaemon(boolean on) on) {…} {…} public final boolean isDaemon() {…} public final boolean isDaemon() {…} …… }} Priorität eines Thread ist unabhängig davon, ob er Daemon oder User Thread ist Zustandsübergangsdiagramm für Threads •getState – Statusabfrage eines Thread (erzeugt, laufend, blockiert, beendet usw. •getState unterscheidet READY und RUNNING nicht – beides ist RUNNABLE! Aktive und passive Klassen •Thread Anwendungen haben aktive und passive Klassen •Aktive Klassen sind die Thread bzw. Runnable Klassen •Die Objekte von passive Klassen werden von den Threads benutzt •Synchronisation (synchronized, wait, notify, notifyAll wird von den passiven Klassen gemacht) Beispiele: Aktive Klassen Bankangestellte Autos Erzeuger und Verbraucher Passive Klassen Bank und Konten Parkhaus Puffer Synchronisationskonzepte aus UNIX •UNIX Synchronisationskonzepte für Prozess ohne gemeinsamen Adressraum •aber auch für Synchronisation innerhalb eines (Java-) Prozesses interessant Semaphor •Das Parkhaus im Parkhausbeispiel ist ein Semaphor •Übliche Bezeichner: Semaphore == ParkingGarage value == places p (passeeren) == enter v (vrijgeven) == leave •Im Unterschied zur Parkhaus Analogie macht auch ein Semaphor mit value = 0 Sinn! Value 0 bedeutet: Semaphor ist von vornherein gesperrt Methode v kann hier den Semaphor öffnen (value++) Value 1 bedeutet: Semaphor ist von vornherein erst mal offen Methode p sperrt hier den Semaphor (value--) Mutex Semaphore •Mutex == mutual exclusion == gegenseitiger Ausschluss •z.B. synchronized Methode bewirkt gegenseitigen Ausschluss •Bei unterschiedlichen Prozessen oder anderen Programmiersprachen gibt’s kein synchronized •Beispiel in Java – lässt sich aber allgemein übertragen class class MutexThread MutexThread extends extends Thread Thread {{ private private Semaphore Semaphore mutex; mutex; public {{ public MutexThread(Semaphore MutexThread(Semaphore mutex) mutex) this.mutex this.mutex == mutex; mutex; start(); start(); }} public public void void run() run() {{ while(true) while(true) {{ mutex.p(); mutex.p(); System.out.println("kritischen System.out.println("kritischen Abschnitt Abschnitt betreten"); betreten"); try try {{ // // kritischer kritischer Zugriff Zugriff auf auf gemeinsame gemeinsame Daten Daten oder oder so so }} catch(InterruptedException e){} catch(InterruptedException e){} System.out.println("kritischer System.out.println("kritischer Abschnitt Abschnitt wird wird verlassen"); verlassen"); mutex.v(); mutex.v(); }} }} }} •Viele •VieleMutex MutexThreads Threadsgreifen greifenauf aufdasselbe dasselbeSemaphor SemaphorObjekt Objektzu zu •Semaphor •Semaphorwird wirdmit miteinem einemPlatz Platzerzeugt! erzeugt!Maximal Maximalein einThread Thread kann kannkritischen kritischenAbschnitt Abschnittbetreten betreten Unterschiede bei UNIX •Gemeinsamer Zugriff auf ein Semaphor Objekt nicht möglich •Semaphor existiert im Betriebssystemkern •Wird durch Kennung (Parameter des des Systemaufrufs) angesprochen •Zugriff auf gemeinsame Daten von Threads unterschiedlicher Prozesse möglich über die Nutzung von gemeinsamen Speicher •Gemeinsamer Speicher wird durch Systemaufrufe eingerichtet siehe auch Herold: Linux/Unix Systemprogrammierung Einfache Semaphore zur Herstellung vorgegebener Ausführungsreihenfolgen Erst soll Thread t1 dann entweder Thread t2, t3 oder t4 (oder alle gleichzeitig) und zum Schluss Thread t5 ausgeführt werden t2 t1 t3 t4 Für jeden Pfeil wird ein Semaphor verwendet t5 Semaphore wirken hier wie Durchlassschranken, die zunächst immer zu sind (value ==0) •Für jeden Pfeil wird ein Semaphor mit Anfangswert 0 verwendet •Alle Threads (t1 bis t5) werden gestartet •t2 bis t5 rufen auf allen jeweils einkommenden Semaphoren p auf und verschwinden daher in Wartezuständen (value == 0) •Nur t1 arbeitet seine „Nutzlast“ an Befehlen ab •Dann ruft t1 auf den einkommenden Semaphoren von t2 bis t4 v auf •t2 bis t4 werden per notify geweckt und in willkürlicher Reihenfolge abgearbeitet •t2 bis t4 rufen auf ihren ausgehenden Semaphoren nach dem Abarbeiten v auf. •Wenn t5 durch alle seine einkommenden Semaphore per notify durchgeschleust wurde, kann er abgearbeitet werden (v und p sind Methoden der Klasse Semaphore) Additive Semaphore •Semaphor kann nicht nur um 1 sondern um beliebige Werte erhöht oder erniedrigt werden •Neue Methode change(int x) (x negativ oder positiv) •Neue Methoden p(int x), v(int x) (x >= 0) •notifyAll statt notify bei Methode v, weil im Falle additiver Semaphore mehrere Threads weiterlaufen können (die z.B. den value jeweils nur um 1 erniedrigen, der value steht jedoch auf z.B. 5) public public void void change(int change(int x) x) {{ if if (x (x >> 0) 0) {{ v(x); v(x); }} else else if if (x (x << 0) 0) {{ p(-x); p(-x); }} }} public public synchronized synchronized void void v(int v(int x) x) {{ if if (x (x <= <= 0) 0) {{ return; return; }} value value += += x; x; notifyAll(); notifyAll(); // // nicht nicht notify! notify! }} •Beispiel (Leser/Schreiber Problem) kommt später Es macht Unterschied, ob value auf einen Schlag um 3 erniedrigt wird oder 3 mal um 1 11mal 33mal mal-3 -3 mal-1 -1 p(x) value Warteschlange 4 3 p(x) value Warteschlange 4 1 2 1 0 1 0 Verklemmung Verklemmung keine keineVerklemmung Verklemmung Semaphorgruppen •Verallgemeinerung der additiven Semaphore (Veränderung von value um mehr als 1) •Änderung aller Semaphoren der Gruppe gleichzeitig um jeweils einen eigenen Wert x •Es wird keine Änderung durchgeführt, wenn auch nur bei einer Semaphore ein negativer value entstünde •Der Versuch einer solchen Änderung führt in Warteschlange Semaphorengruppe -3 -2 1 4 5 1 1 3 2 Semaphorengruppe -3 2 -3 4 5 1 4 5 1 Warteschlange Warteschlange •Anwendungsbeispiel: Philosophenproblem, siehe später public public synchronized synchronized void void changeValues(int[] changeValues(int[] deltas){ deltas){ if(deltas.length if(deltas.length != != values.length) values.length) return; return; while(!canChange(deltas)) while(!canChange(deltas)) {{ try try {{ wait(); wait(); }} catch(InterruptedException catch(InterruptedException e) e) {} {} }} doChange(deltas); doChange(deltas); notifyAll(); notifyAll(); }} Warum notifyAll statt notify? 1. Die durchgeführten Änderungen können mehreren Threads Weiterlaufen ermöglichen (siehe additive Threads) 2. Der „falsche“ Thread könnte geweckt werden Warteschlange Semaphorengruppe 3 0 1 3 0 1 -4 4 1 -4 -2 1 Falscher FalscherThread Threadgeweckt geweckt 040 3 4 1 3 4 1 notify() 2. Der „falsche“ Thread könnte geweckt werden Warteschlange Semaphorengruppe 3 0 1 3 0 1 -4 4 1 4 -2 1 3 040 Bei BeiSemaphorengruppen Semaphorengruppenund und additiven additivenSemaphoren Semaphorenkann kannes es unterschiedliche unterschiedliche Wartebedingungen Wartebedingungengeben: geben:der der erste ersteThread Threadwartet wartetauf auf 1. 1.Semaphore Semaphore-4 -4möglich möglich der derzweite zweiteThread Threadwartet wartetauf auf 2. 2.Semaphore Semaphore-2 -2möglich möglich 0 1 Richtiger RichtigerThread Threadgeweckt geweckt 3 4 1 7 2 2 N Buffer Verallgemeinerung des Erzeuger-Verbraucher Problems auf mehrere Speicherplätze: private private int int head; head; private int tail; private int tail; private private int int numberOfElements; numberOfElements; private int[] private int[] data; data; Am tail wird eingefügt, am head herausgenommen Message Queue •Ein Sender sendet Nachrichten zu einem Empfänger. Dazu werden sie in einer MessageQueue zwischengespeichert •Der Sender ruft send auf um die Nachricht in die Queue einzureihen •Der Empfänger ruft receive auf, um ein Nachricht aus der Queue zu holen •Nachrichten = byte Felder unterschiedlicher Länge •MessageQueue = NBuffer mit einem Feld von byte Feldern (Nachrichten) •Methodennamen: put = send(byte[] msg); get = byte[] receive() •Damit Sender Nachricht nicht manipulieren kann, wenn sie in der Queue ist, wird Kopie gespeichert Konstruktor: public public MessageQueue(int MessageQueue(int capacity) capacity) {{ …… msgQueue msgQueue == new new byte[capacity][]; byte[capacity][]; }} Pipe •MessageQueue ist nachrichtenorientiert, Pipe ist datenstromorientiert •Datenstrom = Grenzen zwischen den byte-Folgen sind aufgehoben – ein einziger byte Strom •-> eindimensionales byte –Feld in NBuffer genügt Pipe, so wie sie bei Oechsle implementiert ist: •Senden = unteilbare Aktion. Wenn Restplatz im Puffer kleiner als Länge der zusendenden Daten -> wait •Wenn Länge der zusendenden Daten > Puffergröße, wird Nachricht geteilt gesendet -> aber Nachrichten können dadurch „durchmischt“ werden !?!?! (nicht bei Oechsle sondern von Java ist auch eine Insel) Java Threads können über Pipes kommunizieren Thread B Thread A write(int b) connect PipedOutputStream PipedWriter PipedInputStream PipedReader int read() ein Byte (0 bis 255) oder -1 •Wenn Ringspeicher in PipedInput voll, dann wartet write(int c) •Größe Ringspeicher Standardmäßig 1024 – seit Java 6 einstellbar Schreiben „auf einen Schlag“: public void write(byte[] b, int off, int len) Writes len bytes from the specified byte array starting at offset off to this piped output stream. This method blocks until all the bytes are written to the output stream. Was, wenn len > 1024? Nichts – denn bytes werden sukzessive rausgeschrieben class PipedOutputStream extends OutputStream { private PipedInputStream sink; public PipedOutputStream( PipedInputStream snk ) throws IOException { /* Auskommentierte Fehlerbehandlung */ sink = snk; tail snk.in = –1; snk.out = 0; head snk.connected = true; } public void write( int b ) throws IOException { if ( sink == null ) throw new IOException( "Pipe not connected" ); sink.receive( b ); } } Receives a byte of data. This method will block if no input is available. public int read(byte[] b, int off, int len) Reads up to len bytes of data from this piped input stream into an array of bytes. Less than len bytes will be read if the end of the data stream is reached or if len exceeds the pipe's buffer size. the method blocks until at least 1 byte of input is available or end of the stream has been detected Parameters: b - the buffer into which the data is read. off - the start offset in the destination array b len - the maximum number of bytes read. Returns: the total number of bytes read into the buffer, or -1 if there is no more data because the end of the stream has been reached. Philosophen Problem •Philosophen essen und denken abwechselnd •Ein Philosoph braucht die zwei Gabeln neben seinem Teller um zu essen •Nicht alle Philosophen können gleichzeitig essen Java Lösung mit synchronized, wait, notifyAll public public synchronized synchronized void void takeFork(int takeFork(int number) number) {{ while(forkUsed[left(number)] while(forkUsed[left(number)] || || forkUsed[right(number)]){ forkUsed[right(number)]){ try { try { wait(); wait(); }} catch(InterruptedException catch(InterruptedException e){} e){} }} forkUsed[left(number)] Nummer des Philosophen forkUsed[left(number)] == true; true; Nummer des Philosophen forkUsed[right(number)] = true; forkUsed[right(number)] = true; }} public public synchronized synchronized void void putFork(int putFork(int number) number) {{ forkUsed[left(number)] forkUsed[left(number)] == false; false; forkUsed[right(number)] = forkUsed[right(number)] = false; false; notifyAll(); notifyAll(); }} }} Jeder Philosoph ein Thread mit while in run() Methode: while(true){ while(true){ think(number); think(number); table.takeFork(number); table.takeFork(number); eat(number); eat(number); table.putFork(number); table.putFork(number); }} Naive Lösung mit Semaphoren •Lösung, die außer Semaphoren keine Synchronisationskonzepte von Java einsetzt •Konzeptionell wie Unix •Jede Gabel = Sempahor mit value = 1 •Vor Essen p Methode auf rechter und linker Gabel •Nach Essen v Methode auf rechter und linker Gabel while(true){ while(true){ think(number); think(number); sems[left].p; sems[left].p; sems[right].p; sems[right].p; eat(number); eat(number); sems[left].p; sems[left].p; sems[right].p; sems[right].p; }} eine einelinke linkeGabel Gabel Diese Lösung kann zu Verklemmung führen – warum? alle Philosophen nehmen gleichzeitig die linke Gabel! Lösung mit Semaphorgruppen • Jede Gabel = ein Semaphor run runMethode Methodeeines einesPhilosopen PhilosopenThread Thread public public void void run(){ run(){ int[] deltas int[] deltas == new new int[sems.getNumberOfMembers()]; int[sems.getNumberOfMembers()]; for(int for(int ii == 0; 0; ii << deltas.length; deltas.length; i++) i++) deltas[i] deltas[i] == 0; 0; int number = leftFork; int number = leftFork; Semaphorengruppe Semaphorengruppe while(true) { while(true) { mit think(number); mitEinsen Einsen think(number); deltas[leftFork] deltas[leftFork] == -1; -1; initialisiert initialisiert deltas[rightFork] = -1; deltas[rightFork] = -1; sems.changeValues(deltas); sems.changeValues(deltas); eat(number); eat(number); deltas[leftFork] deltas[leftFork] == 1; 1; deltas[rightFork] deltas[rightFork] == 1; 1; sems.changeValues(deltas); sems.changeValues(deltas); }} }} •Alle Philosophen nehmen linke Gabel noch möglich? Nein, da immer zwei Gabeln von einem Philosophen auf einen Schlag genommen werden! Leser – Schreiber Problem •Viele Threads greifen lesend oder schreibend auf Datensatz zu •Alle Threads können nur entweder lesen oder schreiben •Lösung: gegenseitiger Ausschluss: Keine Konsistenzprobleme– aber unnötige Einschränkung der Parallelität, denn alle Leser können gleichzeitig zugreifen •Nur ein Schreiber zu einer Zeit möglich Prioritätenproblem: •Leser Priorität: kein Leser greift zu -> schreiben erlaubt (Aktualität der Daten unwichtig) •Schreiber Priorität: kein Schreiber greift zu -> lesen erlaubt (Aktualität der Daten wichtig) •Nicht priorisierter Thread muss auch irgendwann mal drankommen können •Warteliste ermöglicht das in jedem Fall Lösung mit synchronized-wait-notifyAll •Eine Klasse AccessControl –hier sind die Daten, auf die dann Leser/Schreiber Threads zugreifen •Zähler für wartende und aktive Threads public public Object Object read(){ read(){ beforeRead(); beforeRead(); Object Object obj obj == reallyRead(); reallyRead(); afterRead(); afterRead(); return return obj; obj; }} private private synchronized synchronized void void afterRead(){ afterRead(){ activeReaders--; activeReaders--; notifyAll(); notifyAll(); }} private private synchronized synchronized void void beforeRead(){ beforeRead(){ waitingReaders++; waitingReaders++; while(waitingWriters while(waitingWriters != != 00 || || activeWriters activeWriters != != 0){ 0){ try { try { wait(); wait(); bevorzugt }} catch(InterruptedException bevorzugtSchreiber Schreiber catch(InterruptedException e) e) {} {} }} waitingReaders--; waitingReaders--; activeReaders++; activeReaders++; }} Schreiber SchreiberMethoden Methodenanalog analog-- wie wiemuss mussaber aberdort dortdie die Wartebedingung Wartebedingungaussehen? aussehen? Lösung mit additiven Semaphoren •Einfaches Programm ergibt Bevorzugung Leser •Bevorzugung Schreiber auch möglich, aber anderer Ansatz •read Methode wie vorige Folie private private void void beforeWrite(){ beforeWrite(){ sem.p(MAX); sem.p(MAX); }} private private void void afterWrite(){ afterWrite(){ sem.v(MAX); sem.v(MAX); }} private private void void beforeRead(){ beforeRead(){ sem.p(1); sem.p(1); }} private private void void afterRead(){ afterRead(){ sem.v(1); sem.v(1); }} class class AccessControlSem AccessControlSem private private static static final final int int MAX MAX == 1000; 1000; private AdditiveSemaphore sem = private AdditiveSemaphore sem = new new AdditiveSemaphore(MAX); AdditiveSemaphore(MAX); …… }} MAX MAX==maximale maximale Anzahl Anzahlgleichzeitiger gleichzeitiger Leser Leser Schablonen zur Nutzung der Synchronisationsprimitive ok Invariante ok verletzt Methode Zustand A Zustand B synchronized •Mehrere Threads greifen auf Attribute zu •>= 1 Thread ist schreibend -> lesende oder schreibende Methoden synchronized Bedingung für Zustandsänderung (z.B. get von Wert aus Puffer nur, wenn einer da ist) Zustand A Methode Zustand B while Warte-Bedingung mit wait können jetzt Threads weiterlaufen? -> notify, notifyAll Nach rein lesenden Methoden nicht nötig, da Zustand nicht geändert wird! •rein lesende Methoden mit oder ohne wait •schreibende Methoden mit und ohne wait und mit und ohne notify bzw. notifyAll Concurrent-Klassenbibliothek aus Java 5 Thread Pool Vorteil: •nicht für jeden Auftrag muss neuer Thread erzeugt werden •Anzahl der Threads nach oben beschränkbar (vrgl. Denial of Service Angriff) Runnable Objekte (Aufträge) Threads im Pool werden in Puffer gelegt holen sich die Objekte, rufen run auf und arbeiten sie ab, holensich neues Objekt usw. Runnable Objekte werden ausgeführt und geben nix zurück Für Objekte mit Methoden, die was zurückgeben: interface Callable interface interface Callable<V> Callable<V> {{ VV call(); call(); }} Threadpool (z.B. Klasse ThreadPoolExecutor) implementiert Schnittstelle ExecutorService: interface interface ExecutorService ExecutorService {{ <T> <T> List<Future<T>> List<Future<T>> invokeAll(Collection<Callable<T>> invokeAll(Collection<Callable<T>> tasks); tasks); <T> Future<T> invokeAny(Collection<Callable<T>> tasks); <T> Future<T> invokeAny(Collection<Callable<T>> tasks); <T> <T> Future<T> Future<T> submit(Callable<T> submit(Callable<T> task); task); }} •submit wartet nicht auf Ergebniss •invokeAny kehrt zurück, wenn der 1. Auftrag zuende ist – die anderen Aufträge werden abgebrochen Future Auf Aufdas dasErgebnis ErgebnisVV wird wirdgewartet! gewartet! Abbruch Abbruchmöglich möglich obwohl obwohlBearbeitung Bearbeitung schon schonbegonnen begonnenhat hat interface interface Future<V> Future<V> {{ boolean boolean cancel(boolean cancel(boolean mayInterruptWhileRunning); mayInterruptWhileRunning); VV get(); get(); VV get(long get(long timeout, timeout, TimeUnit TimeUnit unit); unit); boolean boolean isCancelled(); isCancelled(); boolean isDone(); boolean isDone(); }} Timeout: Timeout:längste längsteZeit Zeitdie diegewartet gewartetwird wird Unit: Unit:Einheit Einheitvon vontimeout: timeout: TimeUnit.NANO_SECONDS TimeUnit.NANO_SECONDS TimeUnit.MIKRO_SECONDS TimeUnit.MIKRO_SECONDS TimeUnit.MILLI_SECONDS TimeUnit.MILLI_SECONDS TimeUnit.SECONDS TimeUnit.SECONDS ThreadPoolExecutor Java Implementierung eines Thread Pool ThreadPoolExecutor(int ThreadPoolExecutor(int corePoolSize, corePoolSize, int int maximumPoolSize, maximumPoolSize, long keepAliveTime, TimeUnit long keepAliveTime, TimeUnit unit, unit, BlockingQueue<Runnable> workQueue) BlockingQueue<Runnable> workQueue) corePoolSize: mindest Zahl Threads im Pool (selbst wenn unbeschäftigt maximumPoolSize: höchst Zahl Threads im Pool keepAliveTime: max Lebenszeit für unbeschäftigten Thread workQueue: Warteschlange für Aufträge (obwohl nur Runnable werden doch auch Callable Aufträge vom ThreadPool bearbeitet) Locks und Conditions •Lock Objekte haben Methode lock, unlock •lockObject.lock() lässt nur einen Thread durch und schiebt weitere in Warteschlange. lockObject ist dann gesperrt •lockObject.unlock() lässt den nächsten Thread wieder passieren •unlock in finally Block, sonst Dauerlock durch Exception oder return •lock ist wie synchronized aber: •Entsperrung durch unlock z.B. auch in anderen Methoden durch andere Threads möglich •tryLock versucht Sperre und kehrt sofort zurück •Klasse ReentrantLock •ermöglicht es demselben Thread mehrfach dieselbe Sperre zu passieren •ermöglicht faire Bedienreihenfolge der Threads Lese – Schreib Sperre Klasse ReentrantReadWriteLock implementiert ReadWriteLock Lock readLock(); gibt Lesesperre Lock writeLock(); gibt Schreibsperre •eine Lesesperre wird dort angebracht, wo im Code Daten gelesen werden •Schreibsperre entsprechend •Unterschied zu normaler Sperre: •Lesesperren können (zu einem Zeitpunkt) beliebig oft gesetzt werden, Schreibsperren nur einmal •Lese und Schreibsperren sperren sich gegenseitig mit, wenn sie vom demselben ReadWriteLock Objekt stammen Bitte beachten: lesende Threads, schreibende Threads gibt es gar nicht. Der Thread macht immer das, was er an Code in der Methode, wo er ist, gerade vorfindet. Condition Methoden einer Condition implementierenden Klasse Object Condition wait await notify signal notifyAll signalAll •bezieht sich auf ein Lock Objekt •von diesem Objekt wird das Condition Objekt mit newCondition() bezogen zu einem Lock können mehrere Condition Objekte exisitieren •im Produce / Consumer Problem notifyAll nicht mehr nötig •stattdessen signal mit zwei Condition Objekten notFull und notEmpty public public void void put(int put(int x){ x){ lock.lock(); lock.lock(); try try {{ while(count while(count == == data.length){ data.length){ ConditionnotFull.awaitUninterruptibly(); ConditionnotFull.awaitUninterruptibly(); }} Bezeichner: Bezeichner: data[tail++] data[tail++] == x; x; wenn notFull, if(tail == data.length) wenn notFull, if(tail == data.length) tail == 0; tail 0; dann dann Nachricht count++; Nachrichtnur nuran an count++; weitergehen dump(); weitergehen dump(); auf Füllung auf Füllung notEmpty.signal(); notEmpty.signal(); Wartende }} Wartende(nicht (nicht finally { finally { signalAll) signalAll) lock.unlock(); lock.unlock(); }} }} Atomic Klassen AtomicBoolean Atomic Klassen haben get und set AtomicInteger Methoden mit atomarem Zugriff AtomicIntegerArray AtomicLong AtomicLongArray AtomicReference AtomicReferenceArray … Methode zum Vergleichen und Ändern auf „einen Schlag“ (atomar) compareAndSet Beispiel: mehrere Threads versuchen value auf 1 zu setzen. Wer ist erster? public public boolean boolean from0to18() from0to18() {{ if if (this.value (this.value == == 0) 0) {{ this.value this.value == 1; 1; return true; return true; }} else else {{ return return false; false; }} }} Problem: Threadumschaltung zwischen Lesen bei this.value==0 und setzen von value bei this.value = 1 (Warum ist das ein Problem ?) Lösung: AtomicInteger AtomicInteger == …… if if (i.compareAndSet(0, (i.compareAndSet(0, 1) 1) {{ // // Gewinner Gewinner …… }} else else {{ // // Verlierer Verlierer …… }} Synchronisationsklassen Semaphore CountDownLatch mit await warten Threads dass ein Zähler 0 wird. Mit countDown wird Zähler dekrementiert CyclicBarrier Treffpunkt. Mit await warten N-1 Threads. wait Aufruf des Nten Threads befreit alle N Threads zum Weiterlaufen Exchanger An einem Treffpunkt tauschen zwei Threads Datenobjekt aus. V exchange(V x) <- Diese Methode blockiert bis zum Eintreffen des 2. Threads. Sollte nur mit 2 Thread mit einem Exchange Objekt betrieben werden Queues interface BlockingQueue wie vom Producer Consumer Problem bekannt: put, take offer, add: einfügen ohne warten poll: entnehmen ohne warten ArrayBlockingQueue Feld, dessen Größe durch Konstruktor festgelegt wird SynchronousQueue Warteschlange mit 0 Plätzen = Exchanger unidirektional PriorityBlockingQueue Elemente können Comparable implementieren. Sortierung: kleinstes am Kopf LinkedBlockingQueue Liste. Auch „unendlich“ groß möglich. DelayQueue Nur Elemente die Delayed mit getDelay implementieren. Sortierung der Warteschlange nach Delay. Verklemmungen Betriebsmittel: Objekt, auf dessen Benutzung ein Thread u.U. warten muss z.B. wegen Locks, Semaphoren oder synchronized Wieso kann hier eine Verklemmung auftreten? public public void void transferMoney(int transferMoney(int fromAccountNumber, fromAccountNumber, int int toAccountNumber, toAccountNumber, float amount) float amount) {{ synchronized(account[fromAccountNumber]) synchronized(account[fromAccountNumber]) {{ synchronized(account[toAccountNumber]) synchronized(account[toAccountNumber]) {{ account[fromAccountNumber].debitOrCredit(-amount); account[fromAccountNumber].debitOrCredit(-amount); account[toAccountNumber].debitOrCredit(amount); account[toAccountNumber].debitOrCredit(amount); }} }} }} Vorraussetzung und Bedingung für Verklemmungen 1. Die Betriebsmittel sind nur unter gegenseitigem Ausschluss nutzbar 2. Benutzte Betriebsmittel können dem Thread nicht entzogen werden 3. Thread besitzen bereits Betriebsmittel und fordern weitere an Unter diesen Vorraussetzungen kommt es zur Verklemmung genau dann wenn Es gibt zyklische Kette von Threads, von denen jeder mindestens ein Betriebsmittel besitzt, das der nächste Thread in der Kette benötigt Kreis: Thread Rechteck: Betriebsmittel Pfeil von Betriebsmittel zu Thread: Thread besitzt und blockiert Betriebsmittel Pfeil von Thread zu Betriebsmittel: Thread fordert Betriebsmittel an Verklemmung Vermeidung von Verklemmungen Nicht Nichtzu zuvermeiden vermeiden 1. 2. 3. Die Betriebsmittel sind nur unter gegenseitigem Möglich Möglichaber aberaufwendig aufwendig Ausschluss nutzbar Benutzte Betriebsmittel können dem Thread nicht entzogen werden Threads besitzen bereits Betriebsmittel und fordern weitere an Ein Thread darf nur Betriebsmittel fordern, Ein Thread darf nur Betriebsmittel fordern, wenn wenner erkeine keinehat hat Es gibt zyklische Kette von Threads, von denen jeder mindestens ein Betriebsmittel besitzt, das der nächste Thread in der Kette benötigt •Ein •EinThread Threadfordert fordertBetriebsmittel Betriebsmittelimmer immerin inder dergleichen gleichenReihenfolge Reihenfolge an an-> ->keine keineZyklen Zyklen(mit (mitThreads Threadsder dergleichen gleichenKlasse?) Klasse?) •Bei •BeiAnforderung Anforderungvon vonBetriebsmittel BetriebsmittelBedarfsanalyse, Bedarfsanalyse,ob obes esim im schlimmsten schlimmstenFall Fallzu zuVerklemmung Verklemmungkommen kommenkann kann Haben beim Philosophenproblem die Gabeln alle denselben Betriebsmitteltyp? Nein, denn die Philosophen Threads brauchen jeweils bestimmte Gabeln (die benachbarten) um essen zu können. Jede Gabel ist ein eigener Betriebsmitteltyp! Betriebsmittelverwaltung Threads holen sich Betriebsmittel über ResourceManager Thread 1 0 1 int[] release 1 0 1 Betriebsmitteltyp BetriebsmitteltypAA ResourceManager (Semaphorengruppe) int[] int[] acquire Betriebsmittel 4 3 1 Betriebsmitteltyp BetriebsmitteltypCC BetriebsmitteltypBB Betriebsmitteltyp Anfordern von Betriebsmitteln „auf einen Schlag“ Ein Thread darf nur Betriebsmittel fordern, wenn er keine hat -> er fordert alles was er braucht auf einen Schlag Verklemmung verhindert : T Dies kann nicht entstehen B B T sondern nur dies -> keine Zyklen möglich B B •ResourcenManager als Semaphorengruppe, Threads sorgen selbst für Anforderung „auf einen Schlag“ •ResourcenManger führt Buch über Anforderungen der Threads. Wird mit deren Freigaben abgeglichen •Wenn Thread anfordert, obwohl er noch Betriebsmittel hat, Mitteilung an Thread. Thread gibt alles frei und fordert alles neu an Anfordern von Betriebsmitteln gemäß vorgegebener Ordnung •Betriebsmitteltypen werden durchnummeriert •Thread darf nur Betriebsmittelnummer fordern, wenn sie höher ist als alle Nummern, die er schon hat. •Verklemmung verhindert : Angenommen, es gibt einen Zyklus Dann muss es diesen Teilgraphen geben: B Typ i1 T B Typ i2 •Mit obiger Bedingung gilt; i2 > i1 •Wenn man dem Zyklus folgt muss gelten i1 < i2 < i3 … <iN < i1 •Dies ist ein Widerspruch -> kein Zyklus möglich •ResourcenManager als Semaphorengruppe, Threads sorgen selbst für Anforderung von „immer höheren“ Typen •ResourcenManger führt Buch über Anforderungen der Threads. Wird mit deren Freigaben abgeglichen •Wenn Thread zu niedrigen Typ anfordert, Mitteilung an Thread. Thread gibt alle 5höhere Typen frei und fordert alles neu an Wieso löst die obige Strategie das Verklemmungsproblem bei den Philosophen (alle Philosophen wollen linke Gabel zuerst nehmen)? Durch die obige Strategie wird der Philosoph mit Nummer N-1 gezwungen, die rechte Gabel zuerst zu nehmen. Anfordern von Betriebsmitteln mit Bedarfsanalyse Jeder Thread muss von vorneherein wissen, wie viele Exemplare er von jedem Typ höchstens gleichzeitig braucht max 2 max 2 Zugriff Zugriffnicht nichterlaubt, erlaubt,da daThread ThreadAAnoch noch einen einenfordern fordernkönnte könnte Verklemmung Thread A Thread B Belegungszustand ist genau dann sicher: •es existiert mind. ein Thread, der noch fertig laufen kann •mit den dann freiwerdenden Betriebsmitteln wird mind. ein Thread fertig •usw. bis zum letzten Thread •Dies gilt für alle Typen gesondert Beispiel (für nur einen Betriebsmittel - Typ): Anzahl eines Betriebsmittels: 10 momentane Belegung: Restforderung: Thread A 3 Thread B 2 Thread C 2 Belegungszustand Belegungszustand Thread A 6 Thread B 2 Thread C 5 Anzahl freier Betriebsmittel: 3 Kann ein Thread mit seiner Restforderung zuende laufen? Ja, nämlich Thread B: braucht 2 und 3 sind noch da. Kann, wenn B zuende ist, ein weiterer zuende laufen? Wenn B zuende sind 5 Betriebmittel frei – reicht für C. Kann, wenn C zuende ist, der A zuende laufen? Ja, da dann 7 Betriebsmittel frei sind, A aber nur noch 6 braucht. Alle Threads können fertig werden: Zustand ist sicher! Darf Thread A noch ein Betriebsmittel kriegen? Anzahl eines Betriebsmittels: 10 momentane Belegung: Restforderung: Thread A 4 Thread B 2 Thread C 2 Thread A 5 Thread B 2 Thread C 5 Anzahl freier Betriebsmittel: 2 Kann einer mit seiner Restforderung zuende laufen ? Ja, nämlich Thread B: braucht 2 und 2 sind noch da. Kann, wenn B zuende ist, ein weiterer zuende laufen ? Wenn B zuende sind 4 Betriebmittel frei – reicht weder für A noch für C – Verklemmung möglich! Zustand nicht sicher! A darf nichts mehr kriegen. Petri-Netze http://pipe2.sourceforge.net/about.html •Petri Netz = Graph aus Stellen (Kreis), Transitionen (Rechteck) und Pfeilen •Pfeil darf nur von Stelle zu Transition oder umgekehrt gehen •Stellen dürfen keins, eins oder mehrere Marken (Punkte) haben •in sukzessiven Zeitschritten werden Transitionen geschaltet •höchstens 1 Transition pro Zeitschritt! evtl. Prioritäten Transition Transitiont1, t1,t2 t2schaltet schaltet Transition Transitiont3 t3schaltet schaltetnicht nicht •Schaltung, wenn alle eingehende Stellen mind. eine Marke haben •Transitionen ohne Eingänge dürfen immer schalten •nach Schaltung: alle eingehenden Stellen Zahl der Marken — alle ausgehenden Stellen Zahl der Marken ++ Modellierung von Nebenläufigkeit mit Petri-Netzen: synchronized Transition = Verzweigungsbefehl oder Thread Erzeugung/Methodenaufrufe, Methodenende und Threadenden Stelle = Position unmittelbar vor oder nach Verzweigungsbefehl oder Thread Erzeugung/Methodenaufruf („zwischen den Befehlen“) Marke = Thread oder Sperre von Objekt Petri-Netze: synchronized Petri-Netze: notify Priorität Prioritätwakeup1 wakeup1>>Priorität Prioritätnotify2 notify2 Petri-Netze: notifyAll Priorität Prioritätwakeup1 wakeup1>>Priorität Prioritätnotify2 notify2 GUI Parallelität und grafische Benutzeroberflächen Objektorientierte Umsetzung einer graphischen Bedienoberfläche Rahmen (JFrame) Knopf (JButton) Panele (JPanel) Etikett (JLabel) GUI Objektorientierte Umsetzung einer graphischen Bedienoberfläche Zwei Arten von Komponenten: Behälterkomponenten (Container) elementare Komponenten Beispiel: •Rahmen, Panele Beispiel: •Etiketten, Knöpfe •Canvas / Zeichenfläche •Auswahlkomponenten (CheckBox, Liste) •Rollbalken •Textkomponenten GUI Objektorientierte Umsetzung einer graphischen Bedienoberfläche Behälterkomponenten (Container) besitzen Methoden add und remove Fenster sind besondere Behälter: sie „liegen ganz außen“ d.h. sie können in keinen Behälter eingefügt werden. Hauptfenster (JFrame) Dialogfenster (JDialog) sind anderem Fenster zugeordnet können blockieren (modal) Grafische Benutzeroberflächen starten import import javax.swing.*; javax.swing.*; public public class class LabelExample1 LabelExample1 {{ public public static static void void main(String[] main(String[] args) args) {{ JFrame JFrame ff == new new JFrame("Beispiel JFrame("Beispiel für für Label"); Label"); f.add(new JLabel("Hallo Welt")); f.add(new JLabel("Hallo Welt")); f.setLocation(300, f.setLocation(300, 50); 50); f.setSize(400, f.setSize(400, 100); 100); f.setVisible(true); f.setVisible(true); }} }} GUI Die Ereignis Objekte enthalten Informationen über das Ereignis, z.B. an welcher Komponente ist das Ereignis aufgetreten (Abfrage mit getSource) Ereignissteuerung 1. Jedes Objekt kann sich bei Komponenten als Beobachter (Listener) von verschiedenen Ereignissorten anmelden. 2. Wenn ein Ereignis E an einer Komponente K auftritt, werden alle Beobachter benachrichtigt, die für E an K angemeldet sind. GUI Beispiel: Anklicken von Knopf -> Ausgabe „Schaltfläche betätigt“ Beobachter Objekte für Ereignisse der Sorte ActionEvent müssen ActionListener implementieren: einzige Methode: actionPerformed class Beobachter implements ActionListener { public void actionPerformed( ActionEvent ereignis ) { System.out.println(„Schaltfläche betätigt“); } } GUI Beispiel: Anklicken von Knopf -> Ausgabe „Schaltfläche betätigt“ Registrieren des Beobachters an der Komponente: ... JButton knopf = new JButton(); ... Beobachter meinBeobachter = new Beobachter(); knopf.addActionListener( meinBeobachter ); ... Tritt am Knopf ein ActionEvent auf, wird die Methode actionPerformed des Beobachter Objekts aufgerufen. Threads und Swing Threads nach Starten von einem oder mehreren JFrame: java.lang.ThreadGroup[name=main,maxpri=10] java.lang.ThreadGroup[name=main,maxpri=10] Thread[main,5,main] Thread[main,5,main] Thread[Java2D Thread[Java2DDisposer,10,main] Disposer,10,main] Thread[AWT-Shutdown,5,main] Thread[AWT-Shutdown,5,main] Thread[AWT-Windows,6,main] Thread[AWT-Windows,6,main] Thread[AWT-EventQueue-0,6,main] Thread[AWT-EventQueue-0,6,main] Thread AWT-EventQueue heißt Event-Dispatcher Event-Dispatcher kommt erst nach setVisible(true) -> Es gibt für alle Ereignisse und Fenster nur einen Event Thread Event Dispatcher paintComponent Methoden aller Swing Interaktionselemente alle Listener aus allen Fenstern eines Java Prozesses Problem mit Event Dispatcher Thread public public class class ButtonExample2b ButtonExample2b extends extends ButtonExample2 ButtonExample2 {{ public public void void actionPerformed(ActionEvent actionPerformed(ActionEvent evt) evt) {{ super.actionPerformed(evt); super.actionPerformed(evt); try try {{ Thread.sleep(10000); Thread.sleep(10000); }} catch(InterruptedException catch(InterruptedException e) e) {} {} }} }} Was könnte hier ein Problem sein? Wenn über Button etwas hinweggezogen wird, wird Button nicht neu gezeichnet! Regel1: Ereignisbehandlung möglichst schnell. Kein sleep oder wait •Längere Sachen (z.B. Kommunikation mit anderem Rechner) auf anderem Thread •Fortschritts Meldungen an GUI zurück •Aber Problem: Swing Methoden sind im Allgemeinen nicht threadsafe (= nicht für Benutzung durch parallele Threads geeignet) Regel2: Zugriff auf Elemente der grafischen Oberflache nach setVisible(true) nur durch Event-Dispatcher! Andere Threads beauftragen Event-Dispatcher mit EventQueue.invokeLater oder EventQueue.invokeAndWait Thread Threadmacht machtdirekt direktweiter weiter public public class class EventQueue EventQueue {{ public public static static void void invokeLater(Runnable invokeLater(Runnable doRun) doRun) {...} {...} public public static static void void invokeAndWait(Runnable invokeAndWait(Runnable doRun) doRun) {...} {...} }} Thread Threadwartet, wartet,bis bisdoRun doRun fertig fertigist ist class class IncreaseTask IncreaseTask implements implements Runnable Runnable {{ ... ... public public void void run() run() {{ clock.increase(amount); clock.increase(amount); }} }} class class Ticker Ticker extends extends Thread Thread {{ ... ... public public Ticker(Clock Ticker(Clock clock) clock) {{ incrTask incrTask == new new IncreaseTask(clock, IncreaseTask(clock, TICK_TIME TICK_TIME // 1000.0); 1000.0); start(); start(); }} public public void void run() run() {{ try try {{ while(!isInterrupted() while(!isInterrupted() {{ EventQueue.invokeLater(incrTask); EventQueue.invokeLater(incrTask); Thread.sleep((int) Thread.sleep((int) TICK_TIME); TICK_TIME); }} }} catch(InterruptedException catch(InterruptedException e) e) {} {} }} }} Situation bei Grafiken Neuzeichnen einer Grafik soll durch Thread angestoßen werden repaint aufrufen -> Event Dispatcher ruft paintComponent auf repaint ist thread safe Grafikprogrammierung public public class class DrawingExample DrawingExample extends extends JPanel JPanel {{ public public void void paintComponent(Graphics paintComponent(Graphics g){ g){ super.paintComponent(g); super.paintComponent(g); g.setColor(Color.RED); Nicht vom Programm g.setColor(Color.RED); Nicht vom Programm g.drawLine(30, g.drawLine(30, 200, 200, 200, 200, 200); 200); aufrufen, …… aufrufen,sondern sondernvom vom }} Event-Dispatcher! Event-Dispatcher! }} MVC (Model, View, Controller) Listener Controller Interaktionselemente Listener View Modell Socket Programmierung Steuerung logischer Verbindung en, RPC Wohl mitunter einer der populärsten Sprüche lautet „Please Do Not Throw Salami Pizza Away“ (Physical Layer, Data Link Layer, usw.), eine deutsche Variante ist „Alle deutschen Schüler trinken verschiedene Sorten Bier“ (Anwendungsschicht, Darstellungsschicht, …). Datagramm steht als Oberbegriff für OSI-Schicht Schicht 2 Schicht 3 Schicht 4 Datagrammbezeichnung Datenframe Datenpaket Datensegment Socket Programmierung Kommunikation von unterschiedlichen Prozessen auf verschiedenen Rechnern Adressierung auf der IP-Adresse Vermittlungs Schicht Rechner Adressierung auf der Portnummer Transport Schicht Prozess auf Rechner IP-Adresse und Rechnernamen •IP-Adresse v4 32bit (v6 128bit) •je 8 bit als Dezimalzahl: z.B. 143.93.53.147 •Für Menschen besser Rechnernamen: z.B. www.hs-furtwangen.de •Domain Name System setzt Rechnernamen in IP-Adressen um •Adresse 127.0.0.1 oder Rechnername localhost ist immer der eigene Rechner Transportprotokolle UDP (User Datagramm Protocol) TCP (Transmisstion Control Protocol) Verbindungslos Verbindungsorientiert Verlust von Paketen oder Vertauschungen in der Reihenfolge möglich Kein Verlust oder Vertauschung von Daten. Flusskontrolle, Überlastkontrolle Ankommende Daten werden an die Anwendungen verteilt Dazu fügt UDP der IP Funktionalität eine Portnummer hinzu Ankommende Daten werden an die Anwendungen verteilt. Portnummern Alle Daten werden in einem Datagramm Datenstromorientiert. (Pipe) versendet mit Ziel- und Quellportnummer (MessageQueue) Protokoll für Audio, Video Daten www, email Multicast möglich kein Multicast Verbindungsauf- und abbau entfällt Verbindungsauf- und Ab-bau Socket Anwendungsschicht Betriebssystem Socket - Schnittstelle Transportschicht Anwendungen Standarddienste: Standarddienste: wohlbekannte Portnummern wohlbekannte Portnummern Server Client Client Client Client Client beliebige beliebigePortnummern Portnummern Binden von Portnummer an Socket (gilt für TCP und UDP): •Datagramme über Socket senden ->Datagramme Quellportnummer= Socketnr •Datagramme über Socket empfangen ->Datagramme Zielportnummer= Socketnr TCP und UDP Portnummern sind unabhängig voneinander UDP TCP Verbindungsaufbau mit IPAdresse+Port (solange blocked!) Weitere Anfragen ohne IPAdresse+Port Server Antwort über die Verbindungsannahme = Erzeugung von IP-Adresse+Port neuem Socket nur für diese Verbindung – alle diese Sockets haben die gleiche der Anfrage Portnummer! Client Anfrage mit IPAdresse+Port senden Wie Wiekommen kommendie dieTCP TCPDaten Datenan anden denrichtigen richtigenSocket? Socket? a) a) Verbindungsaufbausegment Verbindungsaufbausegmentsind sindspeziell speziell gekennzeichnet gekennzeichnet––kommen kommenzum zum„ServerSocket“ „ServerSocket“ b) b) Daten DatenSegmente Segmentehaben habenjajaQuellport Quellportund undkönnen können dem demrichtigen richtigenSocket Socketzugeordnet zugeordnetwerden werden Behauptung einerEins zu eins Zuordnung von Socket zu Port ist nicht korrekt! UDP in Java byte[] byte[] DatagramSocket +send(DatagramPacket) +receive(DatagramPacket) DatagramPacket( buffer, buffer.length, receiveAdress receivePort) blockierend blockierend wirkt wie der Speicher für eine Nachricht einer MessageQueue Clientseitigs schliessen der Verbindung über Timeout (Methode von DatagramSocket) soll verhindern, dass bei verlorengeheden Kommandos der Client hängenbleibt UDP in Java in Aktion UDPSocket (utility Programm) im Source Code: Server im Source Code: Client im Source Code: Start Server Start Client TCP in Java erzeugt erzeugtSocket Socketfür fürVerbindung Verbindung ServerSocket +accept } nur Server ! byte byteStröme Ströme Socket +getInputStream +getOutputStream Erzeugen von Socket Objekt stellt TCP Verbindung her InputStream (empfangen) OutputStream (senden) Buffered... Ströme können Laufzeit um mehrere Größenordnungen verbessern Senden von Strings: ein Datenstrom wird geschickt wird. Evtl. mehrere read Aufrufe für einen String nötig -> Trennzeichen sind nötig z.B. NewLine (wie bei HTTP, SMTP, POP) Strom zum Lesen (Schreiben analog): Socket Socket ss == ...; ...; BufferedReader BufferedReader br br == new new BufferedReader(new BufferedReader(new InputStreamReader( InputStreamReader( s.getInputStream())); s.getInputStream())); Lesen: String String message message == br.readLine(); br.readLine(); Schreiben: bufferedWriter.write(„hallo, bufferedWriter.write(„hallo, Welt“); Welt“); bufferedWriter.newLine(); bufferedWriter.newLine(); bufferedWriter.flush; bufferedWriter.flush; Rückgabewert null = Stromende -> Partner hat TCP Verbindung geschlossen TCP in Java in Aktion Server im Source Code: Client im Source Code: Start Server Start Client Kommandos increment und reset können auch direkt per telnet localhost 1250 (putty) geschickt werden! Sequentielle und parallele Server UDP Kann abwechselnd Kommandos von verschiedenen Clients ausführen TCP Ist an einen Client solange gebunden, bis der die Verbindung schliesst Wenn der Client nichts tut, liegt der Server brach Statische Parallelität: mehrere parallele Threads teilen sich die Arbeit – Schalter / Kunden System Dynamische Parallelität: Für jeden Kunden wird ein Thread erzeugt. Bei Verbindungsende endet Thread. Threadanzahl variiert ständig Mischformen möglich Beispiel für dynamischen Server TCP Server erhält Zahl: soviel Sekunden wird er blockiert, dann echot er die Zahl serverSocket serverSocket == new new ServerSocket(1250); ServerSocket(1250); while(true) äußereSchleife Schleife––Server Serverwartet wartetauf aufAnfragen Anfragen while(true) {{ äußere tcpSocket tcpSocket == new new TCPSocket(serverSocket.accept()); TCPSocket(serverSocket.accept()); blockiert blockiert statisch while(true) { String request = tcpSocket.receiveLine(); int secs = Integer.parseInt(request); Thread.sleep(secs*1000); tcpSocket.sendLine(); echo } Schließen weggelassen }} dynamisch new Slave(tcpSocket); Erzeugung ohne Referenzvariable -> wenn Thread stirbt dann auch Objekt dynamisch class class Slave Slave extends extends Thread Thread {{ public public Slave(TCPSocket Slave(TCPSocket socket) socket) {{ this.socket this.socket == socket; socket; this.start(); this.start(); }} public public void void run() run() {{ String String request request == socket.receiveLine(); socket.receiveLine(); while(request!=null) while(request!=null) {{ Stromschlusszeichen, Stromschlusszeichen,wenn wennClient-close Client-close request = tcpSocket.receiveLine(); request = tcpSocket.receiveLine(); int int secs secs == Integer.parseInt(request); Integer.parseInt(request); Thread.sleep(secs*1000); Thread.sleep(secs*1000); tcpSocket.sendLine(); tcpSocket.sendLine(); }} socket.close(); socket.close(); }} }} Paralleler und sequentieller Server in Java in Aktion SequentialServer im Source Code: Paralleler Server im Source Code: Client im Source Code: Start Seq. Server Start Client Start Paralleler Server Paralleler und sequentieller Server in Java in Aktion Gefahr bei dynamischer Parallelität: Denial-of-Service-Angriff viele Threads auf Server erzeugen -> Rechner überlastet Lösung: Beschränkung der Thread Anzahl z.B. mit Thread Pool serverSocket serverSocket == new new ServerSocket(1250); ServerSocket(1250); ThreadPoolExecutor ThreadPoolExecutor pool pool == new new ThreadPoolExecutor(3,3,0L,TimeUnit.SECONDS, ThreadPoolExecutor(3,3,0L,TimeUnit.SECONDS, new new LinkedBlockingQueue<Runnable>()); LinkedBlockingQueue<Runnable>()); while(true) while(true) {{ tcpSocket tcpSocket == new new TCPSocket(serverSocket.accept()); TCPSocket(serverSocket.accept()); dynamisch Task task = new Task(tcpSocket, ...); pool.execute(task); }} class class Task Task extends extends Runnable Runnable {{ public public void void run() run() {{ while(true) while(true) {{ String String request request == tcpSocket.receiveLine(); tcpSocket.receiveLine(); ... ... tcpSocket.sendLine(„“+result); tcpSocket.sendLine(„“+result); }} }} •Mit Socket Programmierung kann man HTTP, SMTP, POP3, IMAP etc. programmieren •In Java Klassenbibliothek gibt schon Utility Klassen für HTTP (URL, URLConnection) und für SMTP und POP3 oder •IMAP Oder im Internet suchen Verteilte Anwendungen mit RMI (Remote Method Invocation) Was ist RMI? •Verteiltes System = Programm läuft auf mehreren Rechnern •Objektorientierung: Objekte senden sich Nachrichten •Idee: Objekte auf Rechner verteilen •Objekte senden sich Nachrichten über Rechnergrenzen hinweg (entfernt) Warum RMI? •Objektorientierter als Sockets •Ob Objekte auf einzigen Rechner oder auf vielen Rechnern soll möglichst wenig auffallen •Damit ist Verteilung „transparent“ (Verteilung ist durchsichtig = nicht sichtbar) •Daher RMI -> einfachere Entwicklung als mit Sockets Stub und Skeleton •Stub und Skeleton: damit entfernter Methodenaufruf aussieht wie lokaler •Stub ist ein Stellvertreter Objekt des entfernten Objekts •Stub-Methoden aufrufbar wie Methoden von lokalem Objekts •Skeleton ruft entsprechende entfernte Methoden auf entsprechendem entferntem Objekt auf •Kommunikation über TCP/IP ist verborgen •Stub und Skeleton auch verborgen: •Stub wird automatisch erzeugt •Skeleton ist Programmteil in RMI Impelementierung •Warum wird Remote erweiterndes interface gebraucht? •Stub und Originalobjekt sind zwei verschiedene Objekte, die aber die gleichen entfernten Methoden haben müssen! Vorgehen 1. Welche Objekte werden von anderen Rechnern aus angesprochen? (= entfernte Objekte) 2. Für entfernte Objekte interface mit allen entfernt aufrufbaren Methoden deklarieren • Schnittstelle extends Remote • Alle Methoden: throws RemoteException 3. Schnittstelle implementieren. extends UnicastRemotObject = Benutzung von außen erlaubt 4. Server programmieren: • erzeugt entfernte Objekte und meldet sie bei registry an 5. Client programmieren • Stubs über registry holen • Verwendet Schnittstellen-Methoden wie lokale Methoden 6. registry auf Server Rechner starten Beispiel: Counter per RMI Entferntes Objekt: interface interface Counter Counter extends extends Remote Remote {{ int int reset() reset() throws throws RemoteException; RemoteException; int increment() throws int increment() throws RemoteException; RemoteException; }} Rückgabe: Rückgabe:aktueller aktuellerZählerstand Zählerstand public public Class Class CounterImpl CounterImpl extends extends UnicastRemoteObject UnicastRemoteObject implements implements Remote Remote {{ public public CounterImpl() CounterImpl() throws throws RemoteException RemoteException {} {} ... ... }} parameterloser parameterloserKonstruktor Konstruktornötig nötig Server: public public Class Class Server Server {{ ...main...{ ...main...{ CounterImpl CounterImpl myCounter myCounter == new new CounterImpl(); CounterImpl(); Naming.rebind(„Counter“, Naming.rebind(„Counter“, myCounter); myCounter); }} }} Anmeldung von myCounter Objekt bei Anmeldung von myCounter Objekt beiregistry registryunter unterder der Adresse Adresse„Counter“ „Counter“ Client: public public Class Class Client Client {{ ...main... ...main... {{ Counter Counter myCounter myCounter == (Counter) (Counter) Naming.lookup(„rmi://localhost/Counter“); Naming.lookup(„rmi://localhost/Counter“); int result = myCounter.reset(); int result = myCounter.reset(); ... ... }} •Holen von Counter Objekt bei registry unter der Adresse •Holen von Counter Objekt bei registry unter der Adresse }} „Counter“ „Counter“ •localhost •localhoststeht stehtfür fürRechnername Rechnername •Das •Dasentfernte entfernteObjekt Objektkann kannwie wieein einlokales lokalesObjekt Objektunter unter der derReferenz ReferenzmyCounter myCounterbenutzt benutztwerden werden •Der •DerRückgabetyp Rückgabetypvon vonlookup lookupist istRemote Remoteund undmuss mussauf auf den deninterface interfaceTyp TypCounter Countergecastet gecastetwerden werden GUIClient: •in actionPerformed NICHT entfernte Methoden aufrufen – sonst Gefahr des GUI Einfrieren! •Aufruf in eigenem Thread •Aktualisieren der GUI nur über Einspeisen von Runnable Objekt in EventQueue (mit invokeLater(Runnable)) •In run() von Runnable Objekt wird GUI aktualisiert Wichtiger praktischer Hinweis rmiregistriy muss in einem Verzeichnis gestartet werden, das die Klassenpfadumgebungsvariable angibt z.B. mit set classpath=.;pada\da\rmi\counter Paralleler und sequentieller Server in Java in Aktion Server im Source Code: Start registry Client im Source Code: Start Server Entferntes Objekt: Start Client Wie funktioniert RMI? •Anmelden = Zuordnung von Server Portnummer, Kennung Obj-id etc. zu Namen z.B. Counter •Stub holt über wohlbekannte Portnummer 1099 und Rechnername von lookup den Server-Port, Objekt Kennung etc. über TCP •Wenn 1099 schon belegt: rmiregistry <neue Portnummer> clientseitig: lookup(rmi://localhost:<neue Portnummer>/Counter) •Starten von registry aus Programm: Registry registry = LocateRegistry.createRegistry(1099); registry.rebind(„Counter“, new CounterImpl()); Wenn jeder Server seine eigene Registry haben soll -> jede Registry braucht eine eigene Portnummer (wie soll der Client sonst die für ihn passende registry finden?) Parallelität bei RMI Aufrufen aufruf 1 Client objekt1 aufru f2 Server objekt2 Werden aufruf 1 und aufruf 2 vom Server parallel oder sequentiell abgearbeitet? Parallelität bei RMI Aufrufen aufruf 1 Client Objekt 1 aufru f2 Server Objekt 2 •aufruf 1 und aufruf 2 werden in unterschiedlichen Threads parallel abgearbeitet! (objekt1 und objekt2 können auch von derselben Klasse sein •Im Vergleich zum parallelen TCP Server müssen keine Threads erzeugt und gestartet werden, da dies bereits automatisch gemacht wird Was ist hier beim Programmieren zu beachten? () s a W h c ma Client s() a W mach Objekt 1 Server •aufruf 1 und aufruf 2 von machWas() werden parallel in zwei Threads durchgeführt •Die Methode machWas() muss eventuell synchronized sein Wertübergabe für Parameter und Rückgabewerte •Parameter und Rückgabewerte mit primitiven Datentypen werden als Kopie übergeben (call by value) •-> Änderungen auf der Kopie haben keine Auswirkungen auf das Original •Parameter und Rückgabewerte mit Objekt – Datentypen werden als Kopie übertragen, wenn sie KEINE entfernten Objekte sind (d.h. nicht UnicastRemoteObject erweitern bzw. ein Remote interface implementieren •Entfernte Objekte werden als Referenz übertragen (call by reference), wobei beim Empfänger des Objekts ein Stub erzeugt wird (bei jedem Aufruf ein neuer Stub!) •Werden von solchen Objekten Methoden beim Empfänger aufgerufen (callback) wird beim Sender KEINE Registry benötigt, über die der Empfänger das entfernte Objekt identifizieren müsste Objektreferenz bei nicht entfernten Objekten •Parameter und Rückgabewerte mit Objekt – Datentypen werden als Kopie übertragen, wenn sie KEINE entfernten Objekte sind d.h. nicht UnicastRemoteObject erweitern bzw. ein Remote interface implementieren •-> Änderungen auf der Kopie haben keine Auswirkungen auf das Original •Das gesamte Objektgeflecht wird serialisiert übertragen •-> alle Klassen des Geflechts müssen Serializable implementieren, sonst Exception •Serializable schreibt keine Methoden vor •Attribute, die als transient gekennzeichnet sind, werden nicht serialisiert •Übergeben einer Kopie dann sinnvoll, wenn Client Datenstruktur an Server zur Auswertung oder Speicherung •Rein lesender Zugriff •Objekt Serialisierung = Verwandlung von Attributen in byte Folgen Objektreferenz bei entfernten Objekten •Parameter und Rückgabewerte mit Objekt – Datentypen werden als Referenzen übertragen, wenn sie entfernten Objekte sind -> Änderungen geschehen im Original •Entfernte Objekte werden als Referenz übertragen (call by reference), wobei beim Empfänger des Objekts ein Stub erzeugt wird (bei jedem Aufruf ein neuer Stub!) •Werden von solchen Objekten Methoden beim Empfänger aufgerufen (callback) wird beim Sender KEINE Registry benötigt, über die der Empfänger das entfernte Objekt identifizieren müsste •r‘ zeigt auf Stub •Übergabe als Referenz nötig, wenn Server Datenstruktur auf dem Client ändern sollen •Übergabe als Referenz nötig, wenn Seiteneffekt auf dem Client auftreten soll (z.B. Ausgaben auf Bildschirm) Beispiel für Objektreferenz bei entfernten Objekten: Chatten Irgendein Client sagt was -> Server verteilt es auf die anderen Clients ChatServerMain •Erzeugt ChatServer •Anmeldung ChatServer bei Registry ChatClientMain •Holt Stub Referenz auf ChatServer •Erzeugt ChatClient und ChatGUI •Fügt ChatClient beim ChatServer hinzu (addClient(ChatClient)) •Jeder ChatClient hat einen Namen (Nick Name) ChatServerImpl •Speichert alle ChatClients (bzw. deren Stubs) in einer Liste •Wenn ChatClient mit Namen schon da, wird Anmeldung abgelehnt -> dazu werden alle Clients mit getName nach ihrem Namen gefragt •Wenn dabei ein Client nicht mehr existiert (RemoteException), wird Client aus Liste entfernt (mit iter.remove damit beim Iterator nix durcheinander kommt) String String name name == objRef.getName(); objRef.getName(); for (Iterator<ChatClient> for (Iterator<ChatClient> iter iter == allClients.iterator(); allClients.iterator(); iter.hasNext();) { iter.hasNext();) { ChatClient ChatClient cc cc == iter.next(); iter.next(); try try {{ if if (cc.getName().equals(name)) (cc.getName().equals(name)) {{ return return false; false; }} }} catch(RemoteException catch(RemoteException e) e) {{ iter.remove(); iter.remove(); }} }} allClients.add(objRef); allClients.add(objRef); •Bei jedem Aufruf mit einem ChatClient Objekt als Parameter wird ein NEUER Stub gemacht •Wieso wird mit remove(Stub) in der Liste der alte Stub zum entfernen wiedergefunden? •Weil beim Suchen in der List nicht mit == sondern mit equals verglichen wird •Zwei Stubs liefern mit equals dann true, wenn Sie Stellvertreter für dasselbe Objekt sind (selbst wenn == false liefert) ChatClientImpl extends UnicastRemoteObject ChatServerImpl extends UnicastRemoteObject String getName() void print(String msg) Updater implements Runnable JTextArea ChatGUI implements ActionListener ChatServerCaller extends Thread void sendMessage(String clientName, String msg) void removeClient(ChatClient) Warum gibt es Updater und ChatServerCaller? •Updater: GUI EventQueue mit invokeLater •ChatServerCaller: damit die actionPerformed Methode nicht einfriert ChatGUI •Beenden des Clients mit „Ende“ •Damit der Thread zum Abmelden beim Server noch Zeit zum Zuendelaufen hat, wird 100ms gewartet, bevor System.exit(0) public public void void actionPerformed(ActionEvetn actionPerformed(ActionEvetn et) et) {{ ////Abmelden Abmeldenbeim beimServer Server ChatServerCaller ChatServerCaller caller caller == new new ChatCallerServer( ChatCallerServer( ...); ...); caller.start(); caller.start(); try try {{ caller.join(100); caller.join(100); }} catch(InterruptedException catch(InterruptedException e) e) {} {} System.exit(0); System.exit(0); }c }c •RMI Registry muss nur auf Server gestartet werden •Server erhält Referenzen auf Client Objekt mit addClient und nicht durch Naming.lookup RMI Chat Server in Java in Aktion ChatServerImpl im Source Code: ChatClientImpl im Source Code: Start registry Start Server ChatGUI + ChatServerCaller im Source Code Start Client 1 Start Client 2 Transformation lokaler in verteilte Anwendungen mit RMI •Passive Klassen werden zu entfernten Objekten •Semaphor für verschiedene Prozesse auf verschiedenen Rechnern: •Semaphor wird zu entferntem Objekt interface interface Semaphore Semaphore extends extends Remote Remote {{ void void p() p() throws throws RemoteException; RemoteException; void p() throws RemoteException; void p() throws RemoteException; }} •gegenseitiger Ausschluss mit verschiedenen Prozessen möglich •vorgegebene Ausführungsreihenfolge mit verschiedenen Prozessen möglich Asynchrone Kommunikation mit RMI •MessageQueue oder Pipe wird zu entferntem Objekt •Asynchrone Kommunikation: auch nach Ende des Senders einer Nachricht kann ein neu startender Empfänger die Nachricht noch empfangen (z.B. durch MessageQueue auf eigenem Rechner) •Message Oriented Middleware (MOM): kommerzielle Software mit Unterstützung für Transaktion und Persistenz Verteilte MVC Anwendungen mit RMI •Control Objekte: Aufruf von entfernten Methoden in eigenem Thread -> kein Einfrieren GUI •View Objekte über RMI Aufrufe bei Model als Listener registriert Event Dispatcher Thread entferntes entferntesObjekt Objekt entferntes entferntesObjekt Objekt View Model Control Thread Exportieren von Objekten •Dynamisches Umschalten zwischen Wert- und Referenzübergabe Entfernt-machen von Objekt auch durch „exportieren“ möglich class class UnicastRemoteObject UnicastRemoteObject extends extends RemoteServer RemoteServer {{ static static Remote Remote exportObject(Remote exportObject(Remote obj, obj, int int port) port) throws RemoteException throws RemoteException {{ ... ... }} ... ... }} startet Thread ServerSocket / port x hört für obj lokale Referenz obj-Kennung dies ist NICHT die Registry! kann mehrere Objekte enthalten Exportieren von Objekten •Konstruktoren von UnicastRemoteObject rufen exportObjet auf -> Konstruktoren abgeleiteter Klassen müssen RemoteException werfen •Mit unexport(Remote obj, boolean force) Rückgängig machen möglich •Eine Referenzübergabe ist Wertübergabe (Kopie) eines Stubs! 1. Objekt implementiert Remote und ist exportiert: ein Stubobjekt wird serialisiert übergeben (call by reference) 2. 1. trifft nicht zu, aber Objekt implementiert Serializable -> Objekt wird serialisiert kopiert (call by refernce) 3. Weder 1. noch 2. -> Exception bei Übergabe Nachteil gegenüber extends UnicastRemoteObject: hashCode(), equals(), toString() sind nicht automatisch implementiert - schlecht bei Hashtables vrgl. ChatServer Tabelle Stub und Registry Server-Programm Naming.rebind(stub) RMI-Registry Name des Server Rechner Adresse Objekts für Portnummer des Server stub Registry objekt - Kennung Kopieren des stub in Registry kann über objektKennung das richtige Objekt und Methoden ansprechen Kopieren des stub auf Client Client-Programm Naming.lookup() stub •RMI Objekte sind für CORBA Clients aufrufbar •RMI geht auch über HTTP (falls andere Ports von der Firewall blockiert werden) Migration von Objekten public public class class CounterImpl CounterImpl implements implements Counter, Counter, Serializable Serializable {{ public public Counter Counter comeBack() comeBack() throws throws RemoteException RemoteException {{ UnicastRemoteObject.unexportObject(this, UnicastRemoteObject.unexportObject(this, true); true); return this; return this; }} ... ... }} da damit mitcomeBack comeBackgerade geradeein einRMI RMIAufruf Aufrufläuft, läuft,muss mussdas das Unexportieren Unexportierenerzwungen erzwungenwerden werden public public class class MigratorImpl MigratorImpl extends extends UnicastRemoteObject UnicastRemoteObject implements implements Migrator Migrator {{ public public Counter Counter migrate(Counter migrate(Counter counter) counter) throws throws RemoteException RemoteException {{ UnicastRemoteObject.exportObject(counter, UnicastRemoteObject.exportObject(counter, 0); 0); return counter; return counter; }} ... ... System }} Systemkann kannPortnummer Portnummerselbst selbstwählen wählen •Mediator reicht die Methoden von Counter durch •zusätzlich migrate() public public class class Mediator Mediator {{ private private Counter Counter counter; counter; public void migrate(String public void migrate(String host) host) throws throws RemoteException RemoteException {{ Migrator Migrator migrator migrator == (Migrator) (Migrator) Naming.lookup(„rmi://“ Naming.lookup(„rmi://“ ++ host host ++ „/Migrator“); „/Migrator“); counter counter == migrator.migrate(counter); migrator.migrate(counter); }} ... ... }} Objekt Migration in Java in Aktion Mediator Client CounterImpl MigratorImpl im Source Code: im Source Code: im Source Code im Source Code Start Server Start Client Server Laden von Klassen über das Netz Client interface U extends Remote Client kennt U, weil: m(X x); U u = (U) Naming.lookup(...) möglich sein muss class V implements U m(X x) {x.k();} Client kennt X, weil X x = new X(); u.m(x); möglich sein muss class Y extends X k() // überschreiben von k class X k() Server kennt Y nicht, muss aber k aus Y ausführen können wenn Client m(X x) auf Y Objekt aufruft -> Klassencode nachladen Skeleton Seit java 1.2 gibt es eine generische Skeleton Klasse, die mit Reflektion aus den vom Client geschickten (String) Angaben über Objekt-Kennung, Methodenname, Parameterliste die richtige Klasse und Methode auf dem Server besorgt und aufruft Stub Seit Java 5 werden Stubs dynamisch generiert. Zuerst ein Class Objekt, dass alle Methoden aller Schnittstellen des entfernten Objekts hat. Es existiert keine Class Datei auf der Festplatte! Von dem Class Objekt wird ein Objekt – der Stub generiert. Alle Methoden des Stub rufen die Methode invoke eines InvocationHander Objekt auf, dass die Parameter und Methodenname und Obj. Kennung an RMI Server schickt Sicherheit Verschlüsselung mit SSL (Secure Socket Layer) exportObject UnicastRemoteObject - Konstruktor •Variante mit Parametern RMIClientSocketFactory und RMIServerSocketFactory nutzen •Als Parameter eigene Ableitungen dieser Klassen benutzen oder SslRMIClientSocketFactory und SslRMIServerSocketFactory RMI - Aktivierung •RMI-Server erst dann starten, wenn er gebraucht wird + nach langer Pause beenden •RMI-Server nach Absturz automatisch neu starten rmid-Aktivierungsserver Programm: Anmeldung von RMI-Klassen bei rmid-Server selbst geschriebener Server RMI-Klasse Activatable.register(desc); anmelden rmid-Server spezielles RMI-Objekt eintragen Ende Registry anfordern Client aktivieren RMI-Server Log Datei für Wiederstart http://java.sun.com/j2se/1.4.2/docs/guide/rmi/activation/activation.1.html Verteilte Abfallsammlung Client 2 Client 1 Stub 2 Stub 1 RMI-Server lokale Referenz RMI-Objekt •Stubs senden periodisch Signal an Server •Wenn timeout bei Signal überschritten -> Stub existiert nicht mehr •keine lokalen und entfernten Referenzen -> RMI-Objekt wird gelöscht Webbasierte Anwendungen mit Servlets und JSP Dyamisches / Statisches Web statisch dynamisch html Seite HTTP (Hyper Text Transfer Protocol) Browser erhält eine URL (Universal Resource Locator): <Protokoll>:// <Servername> :<Portnummer><Pfad zur Resource> TCP Verbindung zum HTTP-Serverprogramm •HTTP ist ein ASCII Protokoll •Anfrage / Antwort Paradigma Browser sendet dann z.B. folgende HTTP Anfrage (HTTP-Request): GET /eva/hallo.html HTTP/1.0 <Leerzeile! = Ende der Anfrage> Antwort des Browsers (HTTP-Response) Statuscode: Statuscode: HTTP/1.1 200 OK 2xx 2xx==alles allesin in <Name>:<Wert> Ordnung Ordnung <Name>:<Wert>> 4xx 4xx==Fehler Fehler ... <Leerzeile = Trennung von Head und Datenteil> <Datenteil z.B. eine HTML Seite> Connection:close im Header -> Server schließt unmittelbar nach Antwort TCP Verbindung HTTP 1.1 schließt Verbindung nicht gleich, sondern wartet ein paar Sekunden auf neue Anfragen Browser teilt Protokollfähigkeit mit: GET /eva/hallo.html HTTP/1.1 Host: <Rechnername>:<Portnummer> <Leerzeile! = Ende der Anfrage> Sollte man das dann noch Rechnername nennen?! Mehrere Rechnernamen können auf dieselbe IP-Adresse abgebildet werden! Host sagt dann, welcher host nun tatsächlich gemeint ist. Wie ist es, wenn ein HTTP 1.0 Browser anfragt?! Formulare HTML An Andiese dieseURL URLwird wirdFormular Formulargesandt gesandt rel. rel.Adressierung Adressierung==URL-Pfad URL-Pfadvon vonDokument Dokument++ action actionWert Wert bei beigleichem gleichem Namen Namendarf darfnur nurein ein Radiobutton Radiobutton gedrückt gedrücktsein sein HTTP GET/eva/Tee?Besteller=Rainer+Oechsle&Kreditkartennummer= 4711&Teesorte= Assam&Eile=onHTTP/1.1 Accept:*/* ... POST Anderer AndererHTTP HTTPBefehl! Befehl! HTML <form method=„post“ action = „Tee“ HTTP POST /eva/Tee HTTP/1.1 ... Content-Type:application/x-www-form-urlencoded Content-Length:76 Connection:Keep-Alive Header Leerzeile Besteller=Rainer+Oechsle&Kreditkartennummer=4711&Teesorte=Assam&Eile=on Datenteil GET in Favoritenliste speicherbar, da Anfrage „in URL gecodet“ Nachteil: URL auf Bildschirm -> z.B. Kreditkartennr. sichtbar Datenmenge begrenzt POST Anfrage nicht auf Bildschirm sichtbar Datenmenge nicht begrenzt Servlets (Teil der Java EE Distribution) Vergleich Applets / Servlets Applets Servlets Für www gedacht Keine main Methode Keine selbstgeschriebene –Erzeugung von Objekten –Aufruf von ...let Methoden –dies macht die Laufzeitumgebung GUI kein GUI Datenanzeige Datenproduktion (HTML / Bilddaten) Ausgeführt von Web-Server Ausgeführt von Client-Browser Servlets in Gang setzen (deployen) 1. Servlet Klasse programmieren • Ableiten von HttpServlet • doGet, doPost überschreiben • Evtl. init, destroy überschreiben • init – wird aufgerufen bei Aktivierung des Servlets (z.B. bei erstem Ansprechen des Servlets – hängt von Webserver Konfiguration ab) • destroy – Aufruf z.B. bei Herunterfahren des Webservers HTTP Anfrage lesen lesen HTTP Anfrage import import java.io.*; java.io.*; import import javax.servlet.*; javax.servlet.*; import javax.servlet.http.*; import javax.servlet.http.*; HTTP HTTPAntwort Antwort schreiben schreiben public public class class HelloWorldServlet HelloWorldServlet extends extends HttpServlet HttpServlet {{ public public void void doGet(HttpServletRequest doGet(HttpServletRequest request, request, HttpServletResponse HttpServletResponse response) response) throws IOException,ServletException throws IOException,ServletException {{ response.setContentType("text/html"); response.setContentType("text/html"); PrintWriter im PrintWriter out out == response.getWriter(); response.getWriter(); bewirkt bewirkt im out.println("<html>"); out.println("<html>"); HTTP out.println("<head>"); HTTPheader: header: out.println("<head>"); out.println("<title> Contentout.println("<title> HalloWelt HalloWelt </title>"); </title>"); Contentout.println("</head>"); out.println("</head>"); Type:text/html out.println("<body>"); Type:text/html out.println("<body>"); out.println("<h1>HalloWelt</h1>"); out.println("<h1>HalloWelt</h1>"); out.println("Herzlich out.println("Herzlich willkommen"); willkommen"); Zugriff Zugriffauf aufDatenteil Datenteil out.println("</body></html>"); out.println("</body></html>"); von vonHTTP HTTPAntwort Antwort }} }} mit mitPrintWriter PrintWriterStrom Strom 2. Servlet Klasse übersetzen und bei Tomcat Server ablegen z.B. in C:\Programme\Apache Software Foundation\Tomcat 6.0\webapps\eva\WEB-INF\classes Übersetzen: servlet-api.jar vom Tomcat Server wird gebraucht! Aktuelles Verzeichnis: webapps\eva\WEB-INF\classes javac -classpath .;C:\Programme\APACHE~1\TOMCAT~1.0\lib\servlet-api.jar HelloWorldServlet.java 3. Konfiguration auf dem Webserver (Tomcat): (erst alle <servlet> Einträge, dann alle <servlet-mapping> Einträge) <?xml <?xml version="1.0" version="1.0" encoding="ISO-8859-1"?> encoding="ISO-8859-1"?> <web-app xmlns="http://java.sun.com/xml/ns/j2ee" <web-app xmlns="http://java.sun.com/xml/ns/j2ee" version="2.5"> version="2.5"> <display-name> Servlet-undJSP-Beispiele </display-name> <display-name> Servlet-undJSP-Beispiele </display-name> <description> <description> Servlet-undJSP-Beispiele Servlet-undJSP-Beispiele </description> </description> <servlet> <servlet> <servlet-name> <servlet-name> HelloWorld HelloWorld </servlet-name> </servlet-name> <servlet-class> HelloWorldServlet <servlet-class> HelloWorldServlet </servlet-class> </servlet-class> </servlet> </servlet> <servlet-mapping> <servlet-mapping> <servlet-name> <servlet-name> HelloWorld HelloWorld </servlet-name> </servlet-name> <url-pattern> /HalloWelt </url-pattern> <url-pattern> /HalloWelt </url-pattern> </web-app> </web-app> </servlet-mapping> </servlet-mapping> eva ist Anwendung in Tomcat webapps\eva\WEB-INF\classes http://localhost:8080/eva/HalloWelt webapps\eva\WEB-INF\classes ist Startpunkt für Suche nach Java Klassen beim Laden 4. Web Server starten Änderungen am Servlet werden nicht automatisch übernommen -> /conf/context.xml ändern: <Context reloadable=„true“> -> Auf altem Servlet destroy, auf neuem init String: Wert des Eingabefelds null, wenn Name des Eingabefelds nicht existent Rückgabe Zugriff auf Formulardaten <HttpRequest Objekt>.getParameter (String <Name des Formular Eingabefelds>) Servlets in Aktion HTML Page Servlet im Source Code: Servlet compilieren (Wenn Tomcat schon läuft – Reload im Manager nicht vergessen!) Start Tomcat Start HTML http://localhost:8080/eva/Tea.html Parallelität bei Servlets •Alle HTTP Anfragen laufen in eigenen Threads •Lesende und schreibende Zugriffe auf gemeinsame Resourcen daher synchronized Anwendungsglobale Daten •Zugriff verschiedener Servlets derselben Anwendung auf gemeinsame Objekte •HttpServlet vererbt getServletContext •Ein ServletContext beinhaltet Attributliste (getAttribute(String)) Interface ServletContext •servlet communication with its servlet container, •e.g get the MIME type of a file, dispatch requests, write to a log file. •There is one context per "web application" per Java Virtual Machine. •"web application“ = collection of servlets and content installed under a specific subset of the server's URL namespace Beispiel: Zähler •Ein Servlet für increment und eins für reset •gemeinsame Resource Counter – Objekt: public public class class Counter Counter {{ private private int int counter; counter; public public synchronized synchronized int int increment() increment() {{ counter++; counter++; return return counter; counter; }} public public synchronized synchronized int int reset() reset() {{ counter counter == 0; 0; return counter; return counter; }} }} •ResetServlet trägt Counter in Attributliste von ServletContext ein •Problem: wenn IncrementServlet zuerst aufgerufen wird! •Lösung: Änderung von Konfigurationsdatei <servlet> <servlet> <servlet-name>Reset</servlet-name> <servlet-name>Reset</servlet-name> <servlet-class>RestServlet</servlet-class> <servlet-class>RestServlet</servlet-class> <load-on-startup> <load-on-startup> <servlet> Erzeugung Servlet Objekt <servlet> Erzeugung Servlet Objektbereits bereitsbei bei Installieren Installierenbzw. bzw.Neuladen Neuladender derAnwendung Anwendung ResetServlet public public void void init() init() {{ Counter Counter counter counter == new new Counter(); Counter(); ServletContext ServletContext ctx ctx == getServletContext(); getServletContext(); ctx.setAttribute("Counter", ctx.setAttribute("Counter", counter); counter); }} public public void void doPost(HttpServletRequest doPost(HttpServletRequest request, request, HttpServletResponse HttpServletResponse response) response) throws throws IOException, IOException, ServletException ServletException {{ ... ... ServletContext ServletContext ctx ctx == getServletContext(); getServletContext(); Counter counter = (Counter) Counter counter = (Counter) ctx.getAttribute("Counter"); ctx.getAttribute("Counter"); int value = counter.reset(); int value = counter.reset(); out.println("Der out.println("Der Z&auml;hler Z&auml;hler wurde wurde auf auf "" ++ value value ++ "" zur&uuml;ckgesetzt.<p>"); zur&uuml;ckgesetzt.<p>"); ... ... }}