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