Threads - und wie sie in Java implementiert sind Vortrag von Lukas Klimmasch, Stefan Lang und Ralf Schmidt Jena, den 05.12.2011 Was ist ein Thread? • laut Wikipedia: ein Ausführungsstrang oder eine in der Abarbeitung eines Programms • am ehesten mit Nebenläufigkeit zu übersetzen • Idee: - Programm durch Programmierer oder Compiler in mehrere voneinander unabhängige Teile zerlegen - Befehle verschiedener Prozesse, die naturgemäß keine Abhängigkeiten haben, durchmischen • nicht multi-tasking (mehrere Prozesse); Aufteilung eines einzelnen Prozesses verschiedene Ebenen für Threads: - simultanes Multi-Threading (SMT) – ein Prozessorkern, dessen Architektur die scheinbare Abarbeitung mehrerer Threads erlaubt Quelle: Skript Rechnerstrukturen, Prof. Erhard verschiedene Ebenen für Threads: - simultanes Multi-Threading (SMT) – ein Prozessorkern, dessen Architektur die scheinbare Abarbeitung mehrerer Threads erlaubt - BS organisiert Prozesse in Threads Quelle: Skript Rechnerstrukturen, Prof. Erhard verschiedene Ebenen für Threads: - simultanes Multi-Threading (SMT) – ein Prozessorkern, dessen Architektur die scheinbare Abarbeitung mehrerer Threads erlaubt - BS organisiert Prozesse in Threads Quelle: Skript Rechnerstrukturen, Prof. Erhard verschiedene Ebenen für Threads: - simultanes Multi-Threading (SMT) – ein Prozessorkern, dessen Architektur die scheinbare Abarbeitung mehrerer Threads erlaubt - BS organisiert Prozesse in Threads - Programmierer unterteilt Programm in Threads Quelle: Skript Rechnerstrukturen, Prof. Erhard Zweck von Threads überproportionaler Anstieg an elektrischer Leistung und Chip-Fläche im Vergleich zur Rechenleistung Quelle: Skript Rechnerstrukturen, Prof. Erhard effizientere Nutzung der Ressourcen nötig ● Geschwindigkeitszugewinn ● Ein anschauliches Beispiel Prozess: Zubereitung des Weihnachtsessen verschiedene Threads: Gans Klöße Nachtisch nicht: erst die Gans, dann die Klöße, dann der Nachtisch sondern: Gans im Ofen – Klöße zubereiten – um den Nachtisch kümmern Vorteil: alles gleichzeitig fertig Zeit gespart/effizienter gearbeitet Threads in Java • Programmierung von Threads in Java möglich • JVM bildet Threadverwaltung auf BS ab (falls dieses Threads unterstützt) sonst leistet JVM Verwaltung • gute Planung des Programmierers nötig • Nachteil von Threads: hoher Grad an Koordination nötig! • Programmcode wird fehleranfälliger • mögliche Fehlerfälle müssen vom Programmierer behandelt werden • mögliche Fehlerfälle (Bsp): gemeinsam genutzte Ressourcen mehrere Schreibevorgänge falsche Reihenfolge (write after read) Threads in Java erzeugen Die Klasse Thread: Objekte führen über start() den Thread aus jedes neu erzeugte Objekt entspricht einem Thread möglicher Quellcode: Threads in Java erzeugen Die Klasse Thread: Objekte führen über start() den Thread aus jedes neu erzeugte Objekt entspricht einem Thread möglicher Quellcode: Threads in Java erzeugen Die Klasse Thread: Objekte führen über start() den Thread aus jedes neu erzeugte Objekt entspricht einem Thread möglicher Quellcode: Welchen Code startet start()? Threads in Java erzeugen Die Klasse Thread: Objekte führen über start() den Thread aus jedes neu erzeugte Objekt entspricht einem Thread möglicher Quellcode: Welchen Code startet start()? Was ist das? Die Schnittstelle Runnable UML-Diagramm Klassen implementieren Runnable in run(): nebenläufig auszuführenderProgrammcode Runnable-Objekt an Thread übergeben … Kleine Zusammenfassung • Klasse implementiert Runnable • nebenläufiger Programmcode in run() • Objekt der Klasse Thread • start() Thread-Starten und Ausführen weitere Möglichkeit: Klasse Thread erweitern implementiert selbst Runnable stellt run() bereit ● keine Runnable-Übergabe im Konstruktoraufruf nötig ● Aufruf von start() ausreichend ● … Qual der Wahl? Thread-Erweiterung Runnable implementieren • Einfachvererbung ein mögliches Problem • Runnable-Objekte recht flexibel leicht zu übergeben von Threads aus einem Threadpool ausführbar (später) Eigenschaften von Threads ● ● ● Name: Zugriff über getName(), setName() bzw. im Konstruktor Priorität: wird im Konstruktor zugewiesen (zulässige Werte: 1 bis 10, Standard ist 5) Zustand: Zugriff über getState() Zustände von Threads Zustand Bedeutung ● NEW Thread ist neu und wurde noch nicht gestartet ● RUNNABLE Thread läuft in der Java Virtual Machine ● BLOCKED Thread ist z.B. in einem synchronised-Block ● WAITING Thread wartet etwa auf ein notify() ● TIMED_WAITING Thread wartet nach einem sleep()-Befehl ● TERMINATED Thread wurde beendet Threads als Dämonen ● ● ● ● es gibt Threads, die eine Endlosschleife beinhalten sinnvoll, wenn die Threads während der Ausführung des gesamten Programmes im Hintergrund laufen müssen, z.B. Garbage Collector ein Thread, der als Dämon deklariert wurde, wird beendet, sobald das Hauptprogramm beendet wird im Konstruktor: setDeamon(true) Beispiel Dämonen public class DeamonThread extends Thread { DeamonThread(){ this.setDaemon(true); } //Thread wird zum Dämonen @Override public void run(){ while(true){ } } } //Endlosschleife System.out.println(“Ich bin ein Dämon!!“); Beispiel Dämonen public static void main(String[] args{ new DeamonThread().start(); } mögliche Ausgabe: Ich Ich Ich Ich Ich ... bin bin bin bin bin ein ein ein ein ein Dämon!! Dämon!! Dämon!! Dämon!! Dämon!! insgesamt ca. 15 Mal, oder auch gar nichts... Nachteile beim Arbeiten mit Instanzen der Klasse Thread ● ● ● beim Aufruf des Konstruktors wird eine Instanz eines Runnable-Objektes einem Thread-Objekt zugewiesen run()-Methode kann nur ein einziges Mal abgearbeitet werden Rückgabewert von run() ist void... Thread-Pools ● ● ● Interface Executor: kann eine Runnable ausführen Interface ExecutorService: erbt von Executor und bietet einige nützliche Operationen an, wie z.B. den ThreadPool herunterfahren Klasse Executors: liefert je nach Bedarf verschiedene Thread-Pools, z.B. einen Pool mit fester oder wachsender Anzahl an Threads Übersicht Quelle: http://openbook.galileocomputing.de/javainsel9/ Beispiel Thread-Pools public class Printi implements Runnable { String message; public Printi(String text){ } this.message= "\""+text+"\""; @Override public void run(){ //Druckt Nachricht und Namen des Threads aus } } System.out.println(this.message + " gedruckt von " + Thread.currentThread()); Beispiel Thread-Pools public static void main(String[] args{ Printi p1 = new Printi("Nummer 1"); Printi p2 = new Printi("Nummer 2"); ExecutorService executor = Executors.newCachedThreadPool(); executor.execute(p1); executor.execute(p2); executor.execute(p1); executor.execute(p2); } executor.shutdown(); Ausgabe: "Nummer "Nummer "Nummer "Nummer 1" 2" 1" 2" gedruckt gedruckt gedruckt gedruckt von von von von Thread[pool-1-thread-1,5,main] Thread[pool-1-thread-2,5,main] Thread[pool-1-thread-2,5,main] Thread[pool-1-thread-1,5,main] Das Interface Callable ● ● im Prinzip wie Runnable, nur mit call()-Methode und generischem Rückgabewert benötigt submit()-Methode z.B. aus von einem ExecutorService-Objekt Beispiel Callable import java.util.concurrent.Callable; //implementiert Callable mit generischem Datentyp public class CallClass implements Callable<Integer>{ int result; public CallClass(int i){ this.result=i; } @Override public Integer call(){ //hat jetzt Rückgabewert int k = this.result; for(int j=0; j<k; j++){ } } } this.result += this.result; return this.result; Das Futur<V> Objekt ● ● Generischer Datentyp zum Abspeichern von asynchron einkommenden Ergebnissen wichtige Methoden: V get() throws InterruptedException, ExecutionException V get(long timeout, TimeUnit unit) throws InterruptedException,ExecutionException, TimeoutException boolean isDone() boolean cancel(boolean mayInterruptIfRunning) Beispiel Callable public static void main(String[] args{ CallClass c = new CallClass(2); } ExecutorService executor = Executors.newCachedThreadPool(); //generisches Future-Objekt zum Auffangen des Ergebnisses Future<Integer> result = executor.submit(c); //get-Methode holt das Ergebnis System.out.println(result.get()); Ausgabe: 16 Synchronisation Problem: Mehrere Threads arbeiten auf den selben Daten und kommen sich dabei in die Quere Lösung: Threads so synchronisieren, dass keiner den anderen stört Beispiel 1 – gemeinsam genutzte Daten class Name { String firstname; String surname; private int counter; public } void setName (String firstname, String surname) { //kritische this.firstname = firstname; //<-Abschnitte, da this.surname = surname; //<-Vorname und } Nachname zusammengehören, public String getName() { aber zugreifende Threads beim schreiben und lesen Counter++; //<-unterbrochen werden können. Auch der ++ return firstname + " " + surname;//<-Operator ist } kritisch für Threads (Quelle: http://www.fh-wedel.de/~si/vorlesungen/java/OOPMitJava/ Multithreading/Synchronisation.html) ... Name n; // thread 1: // thread 2: ... ... n.setName("Charlie","Chaplin"); n.getName() ... ... n.setName("Jerry","Lewis"); ... Mögliche Ausgaben für n.getName(): "Charlie Chaplin" "Jerry Lewis" "Jerry Chaplin" "Charlie Lewis" Threadsicherheit Definition: Eine Komponente des Programms wird als threadsicher bezeichnet, wenn sie gleichzeitig von verschiedenen Programmbereichen mehrfach ausgeführt werden kann, ohne dass diese sich gegenseitig behindern. Hier keine Synchronisation nötig! Beispiele für threadsichere Klassen: ● Immutable-Klassen: "Klassen, in denen keine schreibzugriffe erfolgen und die Lesezugriffe unproblematisch sind" (z.B. String oder Wrapper-Klassen) ● Jeder Thread bekommt eigenen Stack --> Alle Methoden, die keine Objektvariablen verändern, sind threadsicher Beispiel Threadsicherheit ... public static String reverse( String s ){ return new StringBuilder( s ).reverse().toString(); } ... Jeder Thread wird eine eigene Variablenbelegung für s haben und ein temporäres Objekt vom Typ StringBuilder referenzieren. Quelle:http://openbook.galileocomputing.de/javainsel9/javainsel_14_006.htm#mj0ba218 bd2eaf4ea985fe997b1df29eff Leider sind die meisten Klassen nicht Threadsicher... ● ● ● Klassen mit statischen Attributen (z.B. Die Klasse Thread selbst wenn man sie ableitet und ein statisches Attribut hinzufügt) Auch die meisten Java-Klassen sind nicht Threadsicher Beispiel "DateFormat", auszug aus JavaDoc: "Synchronization. Date formats are not synchronized. It is recommended to create separate format instances for each thread. If multiple threads access a format concurrently, it must be synchronized externally." ● Hier ist Synchronisation nötig! Wie kann Synchronisation in Java erreicht werden? ● Zwei Wege: ● "Schützen" der kritischen Abschnitte ● Warten und Benachrichtigen Schlüsselwort "synchronized" (seit Java 1.0) ● Methoden: synchronized Type method(...) { // der gesamte Rumpf ist synchronisiert mit this ... } ● Blöcke: synchronized ( e ) { // dieser Block wird synchronisiert // mit dem Objekt, das durch e referenziert wird ... } Quelle: http://www.fh-wedel.de/~si/vorlesungen/java/OOPMitJava/Multithreading/ Synchronisation.html Schnittstelle Lock (seit Java 5.0) lock.lock(); //zuschließen { // dieser Block wird synchronisiert } lock.unlock(); //wieder aufschließen ● ● Wichtigste Implementierung: ReentrantLock() (Verhindert, dass sich ein Thread selbst "aussperren kann") Das Schlüsselwort "synchronized" bestimmt Anfang und Ende des kritischen Abschnittes automatisch, bei Lock muss das wieder öffnen explizit angegeben werden Quelle:http://openbook.galileocomputing.de/javainsel9/javainsel_14_006.htm#mj0ba218 bd2eaf4ea985fe997b1df29eff Synchronisation durch Warten und Benachrichtigen Wieder zwei Wege: ● wait() und notify() ● ● ● ● Schnittstelle Condition Seit Java 1.0 ● Methoden der Klasse "Object" ● Funktionieren nur in Zusammenhang mit "synchronized (o)", Seit Java 5.0 Funktioniert nur mit einem Lock() Objekt, welches durch lock.getCondition() ein Condition() Objekt erzeugen kann ... // erster Thread synchronized( o ) { try { o.wait(); // Habe gewartet, kann jetzt loslegen. } catch ( InterruptedException e ) { } ... ... //zweiter Thread ... synchronized( o ) { // Habe etwas gemacht und informiere jetzt meinen Wartenden. o.notify(); // bzw. o.notifyAll() } ... Quelle:http://openbook.galileocomputing.de/javainsel9/javainsel_14_006.htm #mj0ba218bd2eaf4ea985fe997b1df29eff Quelle: http://openbook.galileocomputing.de/javainsel/javainsel_12_005.html#dodtpc5a3affc8970-4884-b250-cfb1c87fde60 Condition condition = lock.newCondition(); ... lock.lock(); Try { condition.await(); } catch ( InterruptedException e ) { } ... Finally { lock.unlock(); } //gleiches Schema wie wait() und notify(), die Synchronisation erfolgt jedoch über ein LockObjekt ... condition.signal(); Quelle:http://openbook.galileocomputing.de/javainsel9/javainsel_14_006.htm#mje2 b58ede047a8963da930acab3fa6052