T HREADS Vorlesung 15 ➜ Java integriert Threads direkt in die Sprache ➜ Motivation für die Benutzung nebenläufiger Threads: z.B. graphische Anwendungen, wo Benutzerdialog gleichzeitig mit Berechnungen im Hintergrund stattfindet Vor- und Nachteile der zweiten Methode: + Kann von anderen Klassen erben ➜ Da sich die thread-safety oft verlangsamend auswirkt, wird auf sie manchmal verzichtet, wie z.B. in der Swing-Bibliothek (näheres dazu in der Übung) VON – Kann Instanzmethoden von Thread nicht direkt benutzen. Umweg: über die Klassenmethode currentThread() erhält man die Referenz auf den aktuellen Thread in der run-Methode K LASSE T H R E A D T HREADS Wie auch andere Konzepte in Java, werden Threads über Klassen bzw. Vererbung zur Verfügung gestellt Erste Methode der Thread-Deklaration in einem Java Programm: Slide 2 ➜ Die Java-Klasse Thread beinhaltet eine (ursprünglich leere) Methode run() ➜ Die Benutzer-Klasse MyThread wird als Unterklasse von Thread deklariert ➜ Die (vererbte) Methode run() wird in MyThread überschrieben class MyThread extends Thread { public void run() { ... } } ... Thread a = new MyThread(); c 2007 BY ➜ Die Benutzer-Klasse MyRunnable implementiert Runnable, indem sie die Methode run() definiert Merke: Java-Klasse Thread implementiert Runnable ebenfalls ➜ Thread-Safety: für viele Klassen gibt es thread-safe Versionen, d.h. man kann alle ihre Methoden in nebenläufigen Threads ohne Zusatzsynchronisation verwenden JAVA : E RBEN S ERGEI G ORLATCH · U NI M ÜNSTER · PARALLELE S YSTEME · VORLESUNG 15 RU N N A B L E ➜ Java-Interface Runnable enthält abstrakte Methode run() Slide 3 ➜ Java stellt spezielle Hilfsmittel für Synchronisation zur Verfügung IN VON Zweite Methode der Thread-Deklaration in Java Programmen: ➜ Die Gesamtimplementierung besteht aus 135 Methoden in vier Klassen und einem Interface T HREADS JAVA : I MPLEMENTIEREN Nachteil der ersten Methode: Abgeleitete Klasse (wie MyThread ) darf von keiner anderen Klasse erben, da Java die Mehrfachvererbung verbietet 5.3. JAVA -T HREADS Slide 1 IN Thread run(){} Slide 4 MyThread run(){...} IN JAVA : E RZEUGEN UND S TAR TEN Ein Thread ist immer ein Objekt der Klasse Thread und wird (wie jedes Java-Objekt) mit new von einem Konstruktor erzeugt: ➜ Bei der ersten Methode: Thread a = new MyThread(); ➜ Bei der zweiten: Thread a = new Thread(new MyRunnable()); ➜ In beiden Fällen kann der Konstruktor ein String-Argument haben (der Name des Threads, wird zum Debuggen benutzt); sonst bekommen die Threads ihre Namen vom System in der Erzeugungsreihenfolge: Thread-1, Thread-2, ... ➜ Beachte: Ein erzeugter Thread existiert zwar, läuft aber nicht ! Thread muß in beiden Fällen explizit gestartet werden: a.start(); was die Ausführung seiner run() Methode veranlaßt. Klassendiagramm in UML-Notation (Unified Modeling Wichtig: Die run Methode läuft parallel (nebenläufig) zu dem Thread (evtl. main), der start() aufgerufen hat. Language) 1 c 2007 BY S ERGEI G ORLATCH · U NI M ÜNSTER · PARALLELE S YSTEME · VORLESUNG 15 2 I MPLEMENTIERUNG Slide 5 VON C O N S1; ...; S N ; END class Ri implements Runnable { public void run() { Si; }} Thread t1 = new Thread(new R1()); ... Thread tn = new Thread(new Rn()); t1.start(); ...; tn.start(); t1.join(); ...; tn.join(); I MPLEMENTIERUNG Slide 7 VON A T O M I C S1; ...; S N END Object lock = new Object(); ... synchronized (lock) { S1; ...; SN; } Vorgehensweise: ➜ Wie schon bei Pthreads, ist es die Aufgabe des Programmierers, jeden potentiellen Gegenpart zu identifizieren und mithilfe von synchronized auszuschließen. ➜ Jede Klasse, deren Instanzen als Threads laufen sollen, wird als implements Runnable deklariert ➜ Es wird ein neues Thread-Objekt erzeugt: An den Konstruktor wird das “Runnable” Objekt übergeben, das parallel ausgeführt werden soll ➜ Die Methode start des neuen Thread-Objekts wird aufgerufen ➜ Das Thread-Objekt startet die run-Methode K RITISCHE B EREICHE MIT S Y N C H R O N I Z E D M OTIVIERENDES B EISPIEL : Bounded Buffer ➜ Übliche Idee: andere Threads vor dem kritischen Bereich (Zugriff auf gemeinsame Variablen) anhalten ➜ Arten von synchronized (Anweisungsblock oder Methode): Beispiel Bounded Buffer: Ein FIFO-Puffer begrenzter Kapazität wird von einem Producer und einem Consumer nebenläufig benutzt: ➜ Producer schreibt und Consumer liest, unabhängig voneinander • synchronized (object) { ... Anweisungsblock ... } Jede Objektinstanz kann als ein Lock benutzt werden Slide 6 ➜ Producer kann nicht schreiben wenn der Puffer voll ist, ➜ Consumer kann nicht lesen, wenn der Puffer leer ist. • synchronized type methodName(...) {... Rumpf ...} ist äquivalent zu synchronisiertem Anweisungsblock: Slide 8 type methodName(...) { synchronized(this) { ... Rumpf ... } } ➜ Die zweite Variante ist die übliche: • Pro Objekt wird immer nur eine synchronized Methode ausgeführt: Der Thread muß dafür das Objekt-Lock erlangen • Alle relevanten Methoden unbedingt synchronisieren: Die nicht-synchronisierten können frei auf Instanzdaten zugreifen! c 2007 BY S ERGEI G ORLATCH · U NI M ÜNSTER · PARALLELE S YSTEME · VORLESUNG 15 3 c 2007 BY S ERGEI G ORLATCH · U NI M ÜNSTER · PARALLELE S YSTEME · VORLESUNG 15 4 M OTIVATION F ÜR BEDINGTE E INSATZ S YNCHRONISATION • Aktive (initiieren Aktionen): werden als Threads implementiert, z.B. Producer, Consumer • Passive (reagieren auf Aktionen): werden als Monitore (geschützte Objekte) implementiert, z.B. Buffer ➜ Gegenseitiger Ausschluß nötig, aber: • Consumer braucht keinen Zugriff/Lock, wenn Puffer leer • Producer braucht keinen Zugriff/Lock, wenn Puffer voll ➜ Implementierungsschema: • In beiden Fällen: wird die Bedingung nicht erfüllt, muß das Lock freigegeben werden, sonst blockiert das Programm Slide 11 ➜ Lösung: Thread wartet (ohne Lock) auf eine Bedingung, die von einem anderen Thread geändert wird; erst dann versucht der aufgeweckte Thread, den Zugriff/Lock erneut zu erlangen IN ➜ Beachte: Da wait eine Ausnahme auslösen kann, muß es immer in einem try-Block aufgerufen werden (hier vernachlässigt) JAVA -M ONITOR : A NSCHAULICH JAVA ➜ Ausführung von wait() im Thread B : den Monitor verlassen, d.h. Lock abgeben und auf notify oder notifyAll warten ➜ Monitor: Abstraktes Sprachkonstrukt für nebenläufiges Programmieren (Ursprung ca. 1972 – C. Hoare, P. Brinch-Hansen) ➜ Ausführung von notify bzw. notifyAll im Thread A : einen bzw. alle wartenden Threads aufwecken (wobei nur ein Thread in den Monitor eintreten darf) ➜ Ein Monitor kapselt Daten, die von (synchronisierten) Prozeduren nur im gegenseitigen Ausschluß modifizierbar sind ➜ Zusätzlich: bedingte Synchronisation (Warten auf Bedingung bzw. Signalisieren der evtl. Änderung der Bedingung) Slide 10 public synchronized void action(){ while (!B) wait(); // modify data notifyAll();} ➜ Beachte: while-Schleife nötig, um die Bedingung nach dem Aufwecken neu zu überprüfen Eine abstrakte Datenstruktur mit gegenseitigem Ausschluß und bedingter Synchronisation wird allgemein als Monitor bezeichnet; Monitore werden in Java (teilweise) unterstützt M ONITORE Monitor ➜ In Java: Slide 12 ➜ Prinzip: Ein Monitor wird in Java als eine Klasse mit ausschließlich synchronized Methoden und geeigneten wait-, notify- und notifyAll-Anweisungen realisiert S ERGEI G ORLATCH · U NI M ÜNSTER · PARALLELE S YSTEME · VORLESUNG 15 wait() notify() • Bedingte Synchronisation mit: wait zum Warten/Schlafen, und notify,notifyAll zum Aufwecken BY 000000000 111111111 111111111 000000000 000000000 111111111 000000000 111111111 000000000 111111111 Data 000000000 111111111 000000000 111111111 000000000 111111111 000000000 111111111 Thread A • Mit synchronized-Methoden kann der Zugriff auf die Variablen eines Objekts mutually exclusive gemacht werden c 2007 M ONITOREN : H ERANGEHENSWEISE ➜ In Anwendungen gibt es i.d.R. zwei Arten von Prozessen: ➜ Producer und Comsumer im BoundedBuffer verwenden gemeinsame Variablen: Pufferzellen, Elementzähler Slide 9 VON ... .. Thread B ➜ Die drei Methoden stehen in class Object zur Verfügung ➜ Sie können nur von einem Thread ausgeführt werden, der bereits das Lock besitzt, d.h. in einer synchronized Methode ist 5 c 2007 BY S ERGEI G ORLATCH · U NI M ÜNSTER · PARALLELE S YSTEME · VORLESUNG 15 6 M ONITOR -S EMANTIK IN B OUNDED B UFFER JAVA ➜ Es gibt eine Warteschlange pro Monitorobjekt/Lock (oder Klasse im Falle von static synchronized) zum Warten auf (evtl. unterschiedliche) Bedingungen ➜ Bei notify (bzw. notifyAll) passiert folgendes: Slide 13 • Ein Thread (bzw. alle) wird aus der Schlange ausgewählt. Auswahl ist beliebig (!): Nicht unbedingt der am längsten Wartende Thread, der mit der höchsten Priorität, etc. • Dem Thread einen Prozessor zuweisen (nicht unbedingt gleich – der aufweckende Thread kann weiter laufen!) Slide 15 ➜ Die wait-Bedingung muß nach Aufwachen neu überprüft werden, weil: • nach notifyAll: evtl. mehrere Threads aufgeweckt, der erste könnte die Bedingung bereits verändert haben • nach notify: kann unterbrochen werden (wait ist nicht atomar!), ein neuer Thread kommt evtl. hinzu, erlangt den Monitor und ändert die Bedingung P RODUCER ➜ Producer schreibt und Consumer liest, unabhängig voneinander ➜ Producer kann nicht schreiben wenn der Puffer voll ist, ➜ Consumer kann nicht lesen, wenn der Puffer leer ist. Slide 16 Slide 14 JAVA class Buffer { private Object[] buf, int in = 0, int out = 0, int count = 0, int size = 0; Buffer(int size) {this.size=size; buf = new Object[size];} public synchronized void put (Object o) { while (count==size) wait(); buf[in] = o; in=(in+1)%size; ++count; notifyAll();} public synchronized Object get () { while (count==0) wait(); Object o=buf[out]; buf[out]=null; out=(out+1)%size; --count; notifyAll(); return(o);} } B EISPIEL : Bounded Buffer Beispiel Bounded Buffer: Ein FIFO-Puffer begrenzter Kapazität wird von einem Producer und einem Consumer nebenläufig benutzt: IN UND C ONSUMER IN JAVA class Producer implements Runnable { ... public void run() { while(true) { ThreadPanel.rotate(12); buf.put(new Character(alphabet.charAt(ai))); ThreadPanel.rotate(348);}}} class Consumer implements Runnable { ... public void run() { while(true) { ThreadPanel.rotate(180); Character c = (Character)buf.get(); ThreadPanel.rotate(180);}}} c 2007 BY S ERGEI G ORLATCH · U NI M ÜNSTER · PARALLELE S YSTEME · VORLESUNG 15 7 c 2007 BY S ERGEI G ORLATCH · U NI M ÜNSTER · PARALLELE S YSTEME · VORLESUNG 15 8 JAVA : P HILOSOPHEN B EISPIEL : D INING P HILOSOPHERS Das berühmteste Beispiel für Nebenläufigkeit [E. Dijkstra 1968] ➜ Das Problem: 5 Philosophen speisen mit 5 Gabeln am runden Tisch, wobei jeder zum Essen 2 Gabeln braucht Slide 17 ➜ Ein Philosoph kommt zu einem zufälligen Zeitpunkt an seinen Platz, nimmt zwei Gabeln und ißt beliebig lange Slide 19 ➜ Frage: Wie den Gabelzugriff regeln, ohne gegenseitiges Blockieren, Verhungern und unnötiges Warten? JAVA : I MPLEMENTIERUNG MIT T HREADS UND JAVA : G ABEL Slide 20 ➜ Drei Zustände bei Threads: • Essen (grün) • Denken/satt (gelb) • Warten/hungrig (blau) BY S ERGEI G ORLATCH · U NI M ÜNSTER · PARALLELE S YSTEME · VORLESUNG 15 class Philosopher extends Thread { private int identity; private Fork public void run() { while (true) { // applet changes sleep(...); // thinking right.get(); // hungry, taking sleep(...); // turn left left.get(); // taking left sleep(...); // eating right.put(); left.put();}}} // M ONITOREN ➜ Gabeln sind passiv ⇒ als Monitore modellieren c 2007 T HREADS left, right; are omitted right freigeben Main-Fragment: for (int i=0;i<N;++i) fork[i]=new Fork(display,i); for (int i=0;i<N;++i){ phil[i] = new Philosopher(i,fork[(i-1+N)%N],fork[i]); phil[i].start();} ➜ Philosophen sind aktiv ⇒ als Threads modellieren Slide 18 ALS 9 ALS M ONITOR class Fork { private boolean taken=false; private int identity; Fork(PhilCanvas disp, int id) //Konstruktor {display=disp; identity=id;} synchronized void put() { taken=false; display.setFork(identity,taken); notify();} // Gabel frei synchronized void get() { // Exception m\"oglich! while (taken) wait(); // Warten bis frei taken=true; // Gabel belegt display.setFork(identity,taken);} } c 2007 BY S ERGEI G ORLATCH · U NI M ÜNSTER · PARALLELE S YSTEME · VORLESUNG 15 10 T HEORIE : N OTWENDIGE I MPLEMENTIERUNG : D EADLOCK ? UND H INREICHENDE B EDINGUNGEN Vier Bedingungen [Coffman, Elphick and Shoshani 1971] ➀ Serielle Benutzung von Resourcen (gegenseitiger Ausschluß) Philosophen: Durch Monitore gegeben, ist auch nötig Slide 23 Slide 21 ➁ Inkrementelles Resourcen-Erlangen, d.h. während des Wartens auf eine Resource gibt der Prozeß die bereits erlangten Resourcen nicht frei Philosophen: Die rechte Gabel wird nicht abgegeben, bis die linke erlangt ist ➂ Keine Preemption, d.h. dem Prozeß kann die bereits erlangte Resource nicht (von außen) genommen werden Philosophen: Niemand zwingt zur Abgabe der erlangten Gabel ➃ Wartezyklus existiert, wo jeder Prozeß die Resource hält, auf die sein Vorgänger wartet Philosophen: Zyklischer Wartegraph (Wartezyklus) am runden Tisch, wo jeder die rechte Gabel besitzt und auf die linke wartet D EADLOCKFREIE L ÖSUNGEN D EADLOCK F ÜR P HILOSOPHEN ➜ Asymetrisches Verhalten: “gerade” Philosophen nehmen zunächst die linke Gabel, “ungerade” – die rechte P HILOSOPHEN • Philosophen sind nicht gleichberechtigt: während 0 mit 1 und 2 mit 3 um die erste Gabeln konkurrieren, hat 4 seine erste Gabel sofort und ist somit im Vorteil • Wie konnte das trotz Monitoren passieren? • Situation: Alle werden gleichzeitig hungrig, jeder nimmt die rechte Gabel, und versucht die linke zu holen. Slide 22 F ÜR Slide 24 • Deadlock: jeder wartet auf die linke Gabel, die nur vom Nachbar freigegeben werden kann; dies kann aber nicht passieren, weil der Nachbar auch wartet ⇒ alle warten unendlich lange ... • Bei einigen Schedulingstrategien kann – obwohl unwahrscheinlich – Verhungern entstehen ➜ Das Erlangen beider Gabeln atomar machen: wenn frei – essen, sonst auf einer Bedingungsvariablen warten; gegessen: notify • + Kein Deadlock, alle gleichberechtigt • – Verhungern möglich: wenn Philosophen abwechselnd paarweise essen – 0 mit 2 und 1 mit 3 – wird 4 verhungern • Fazit: Monitore schützen nicht vor Deadlock! ➜ Extra “Butler”-Prozeß : nicht mehr als 4 Philosophen gleichzeitig zum Tisch lassen c 2007 BY S ERGEI G ORLATCH · U NI M ÜNSTER · PARALLELE S YSTEME · VORLESUNG 15 11 c 2007 BY S ERGEI G ORLATCH · U NI M ÜNSTER · PARALLELE S YSTEME · VORLESUNG 15 12 S CHEDULING UND S TEUERUNG VON T HREADS V ERGLEICH : JAVA -T HREADS ➜ Da es potentiell mehr Threads als CPUs gibt, müssen die Threads abwechselnd auf Prozessoren arbeiten ➜ Direkter Einfluß auf Scheduling in Java: Slide 25 POSIX Thread ( Runnable ) , pthread create () t . start (), t . join () pthread join () synchronized () pthread mutex init () pthread mutex unlock () await Slide 27 pthread cond init () obj.wait () pthread cond wait (), obj.notify () pthread cond signal () obj.notifyAll () pthread cond broadcast () ➜ Man muß bei wait nicht den Mutex angeben, der freigegeben werden soll wie in Pthreads ➜ In Pthreads darf signal ohne Lock-Erlangen abgeschickt werden, in Java wäre das für notify ein Fehler Aktuelles Interleaving kann nicht vorhergesagt werden. Man muß deshalb beliebiges Interleaving voraussetzen. VS . Java con pthread mutex lock () ➜ Scheduling von Threads zur Ausführung ist schwer vorhersagbar, da Schedulings-Strategien (cooperative, preemptive, etc.) systemabhängig sind JAVA -T HREADS P THREADS Konzept atomic • Priorisierung möglich: von 1 bis 10, default = 5 (plattform-abhängig) • Thread.yield(); bewirkt einen Thread-Wechsel, aber nur zu einem gleich priorisierten Thread (evtl. wird der gleiche Thread ausgewählt). Macht Sinn nur für non-preemptive Scheduling VS . ➜ Pthread-Mutexe sind etwas fehleranfälliger (mgl. vergessene Unlocks) JAVA -P LATTFORMUNABH ÄNGIGKEIT ➜ Es können keine Annahmen über die Threads-Implementierung auf der konkreten Maschine gemacht werden ➜ Man muß alle Möglichkeiten erwarten, aber darf keine von ihnen voraussetzen! ➜ Programmierer muß sein Programm für zwei widersprüchliche Extremfälle bereit machen: Slide 26 • Ein Thread kann jederzeit unterbrochen werden, d.h. fast alle Operationen sind nicht-atomar und müssen in kritischen Abschnitten mit synchronized abgesichert werden • Ein Thread kann evtl. beliebig lang nicht unterbrochen werden, was bei anderen Threads zum Verhungern führen kann. Lösung: yield oder sleep. ➜ Das ganze Spektrum zwischen den beiden Extremfällen muß als möglich (aber nicht garantiert!) berücksichtigt werden c 2007 BY S ERGEI G ORLATCH · U NI M ÜNSTER · PARALLELE S YSTEME · VORLESUNG 15 13 c 2007 BY S ERGEI G ORLATCH · U NI M ÜNSTER · PARALLELE S YSTEME · VORLESUNG 15 14