Java Threads

Werbung
Java Threads
Mutex, Semaphor, Race Condition, Deadlock, Starvation und Co..
Prof. Dr. Nikolaus Wulff
Java Threads
• Threads sind „leichtgewichtige Prozesse“. Sie
benötigen keine Betriebsystemressourcen wie ein
echter Prozess. Die OS-Ressourcen hält die JVM...
• Jeder Thread durchläuft seriell seinen Programmcode.
• Ein Thread kann blockieren (z.B. System.in.read())
oder aber auch gezielt „schlafen“ gelegt werden:
– z.B. this.wait(1000) oder Thread.sleep(1000)
• Ein Thread kann einen anderen wieder aufwecken:
– this.notify() oder this.notifyAll()
• Damit dies funktioniert, muss der Thread in einer
synchronisierten „Critical Section“ laufen.
© Prof. Dr. Nikolaus Wulff
Informatik III
2
Zeitlicher Ablauf von Threads
• Auf Einprozessormaschinen laufen die Threads
scheinbar parallel, auf Multicoreprozessoren real.
• I.A. wird es immer mehr Prozesse & Threads als
Prozessoren geben.
• Jeder Thread erhält ein Zeitfenster zum Laufen.
Thread
A
B
C
D
© Prof. Dr. Nikolaus Wulff
gerade
aktiv
Informatik III
3
Multitasking Paradigma
• Mehrere Programme (Prozesse) laufen unabhängig
voneinander, können jedoch mit einander
kommunizieren.
– Z.B. im Dateimanager wird per Drag & Drop eine Datei auf
Notepad/Acrobat Reader etc. abgelegt und dort geöffnet...
BetriebSystem
Shared
Memory
© Prof. Dr. Nikolaus Wulff
Anwendung
Anwendung#1
#1
Lokaler Speicher
Anwendung
Anwendung#2
#2
Lokaler Speicher
Anwendung
Anwendung#3
#3
Lokaler Speicher
Informatik III
4
Multithreading Paradigma
• Eine der Anwendungen ist die JVM, die ihrerseits
mehrere Threads verwaltet und synchronisiert.
BetriebSystem
Anwendung
Anwendung#1
#1
Anwendung
Java
JavaJVM
JVM#1
Lokaler Speicher
JVM
Anwendung
Thread
Thread#1
#1#1
Lokale
LokalerVariablen
Speicher
Globale
Variablen
© Prof. Dr. Nikolaus Wulff
Lokaler Speicher
Thread
Thread#2
#2
Lokale Variablen
Thread
Thread#3
#3
Lokale Variablen
Informatik III
5
Eigene Threads
• Java Threads sind Instanzen der Klasse Thread.
• Diese werden mit der „start“ Methode gestartet, ab
dann wird vom Thread dessen „run“-Methode
ausgeführt, die entsprechend überladen werden muss.
• Alternative kann ein Thread mit einem „Runnable“
Objekt parametrisiert werden, dessen „run“-Methode
dann ausgeführt wird.
• Der Thread „lebt“ solange bis die run Methode
beendet ist, entweder wg. einer Exception oder des
natürlichen Endes.
© Prof. Dr. Nikolaus Wulff
Informatik III
6
Thread Verwendung
• Zwei Möglichkeiten einen Thread zu verwenden:
Durch Vererbung oder durch Parametrisierung mit
einem Runnable Objekt.
• In beiden Fällen muss eine geeignete run-Methode
implementiert werden.
© Prof. Dr. Nikolaus Wulff
Informatik III
7
Thread Life-Cycle
• Threads werden in Java als Instanzen der Klasse
java.lang.Thread implementiert.
• Der Thread Lebenszyklus wird geregelt durch die
Methoden:
– start: Beginn der asynchronen Ausführung, diese Methode
startet die run Methode der jeweiligen Thread Instanz.
– run: Die eigentliche Aufgabe des Thread wird in dieser
Methode geleistet. Sobald diese Methode beendet wird,
entweder durch return oder Exception ist der Thread „tot“.
– stop: Beendet die run Methode und stoppt den Thread.
Diese Methode soll nicht mehr verwendet werden, da sie
nicht thread-safe ist...
© Prof. Dr. Nikolaus Wulff
Informatik III
8
Threads als FSM
• Threads können (scheinbar) gleichzeitig parallel auf
einem Prozessor laufen.
• Hierbei hat jeder Thread seinen eigenen Stack für
lokale Variablen und muss sich den Code und
Datenbereich der JVM mit allen anderen Threads
teilen.
• Java Threads werden als Zustandsautomat modelliert.
• Es gibt die fünf wichtigen Zustände CREATED,
TERMINATED, RUNING, RUNABLE und NONERUNABLE, die durch geeignete Statusübergänge mit
Hilfe von Thread Methoden erreicht werden können.
© Prof. Dr. Nikolaus Wulff
Informatik III
9
Thread Statusdiagramm
Aktiv
Schlafend
Tot
• Statusübergänge werden durch entsprechende
Methoden der Thread Klasse ausgelöst.
© Prof. Dr. Nikolaus Wulff
Informatik III
10
Threadsteuerung
• Methoden zum Steuern von Threads sind in den
Klassen Objekt und Thread zu finden:
• Object: wait, notify, notifyAll
• Thread: sleep, interrupt, join, yield, destroy, stop,
suspend, resume
• Einige Methoden sind „Deprecated“ und sollen nicht
mehr verwendet werden. D.h. einige der Thread-Stati
müssen ohne obige Methoden erreicht werden! Infos
hierzu liefert die JDK Dokumentation unter:
Why Are Thread.stop, Thread.suspend, Thread.resume
and Runtime.runFinalizersOnExit Deprecated?
© Prof. Dr. Nikolaus Wulff
Informatik III
11
Ressourcen
• Programme verwenden Ressourcen, diese lassen
sich grob in drei Kategorien unterteilen:
– sharable / teilbar
– consumable / verbrauchbar
– serially resusable / exklusiv wiederverwendbar
• In diesem Kontext ist die letzte Gruppe interessant.
Nur ein Teilnehmer ( ≡ Prozess oder Thread) zur
Zeit darf diese verwenden.
• Typische Beispiele sind: Datenbank- und
Socketverbindungen, der Drucker, die Festplatte,...
© Prof. Dr. Nikolaus Wulff
Informatik III
12
Problematisches Teilen
• Race Condition / Inferenz
– Verschiedene Teilnehmer manipulieren quasi gleichzeitig,
bzw. zeitlich überlappend, die selbe Ressource. Um dies zu
verhindern werden Sperrmechanismen, wie Critical
Section, Mutex und Semaphore, eingeführt.
• Deadlock / Verklemmung
– Die Sperren können dazu führen, das sich zwei Teilnehmer
derart blockieren, das keiner von beiden weiter ausgeführt
werden können...
• Life Lock / Starvation / Verhungern
– Massnahmen gegen Deadlock führen bei ungerechter
Verteilung dazu, dass einige Teilnehmer nie die Ressource
erhalten...
© Prof. Dr. Nikolaus Wulff
Informatik III
13
Vier Bedingungen zur Verklemmung
• Vier Bedingungen sind notwendig, damit es zu einer
Verklemmung kommt:
(1) Wechselseitiger Ausschluss: Nur ein Teilnehmer zur Zeit
hat exklusiven Zugriff auf Ressource A.
(2) Halten und Warten: Der Teilnehmer hält und blockiert
Ressource A während er auf Ressource B wartet.
(3) Keine Verdrängung: Der Teilnehmer kann nur selbst die
Ressource A wieder freigeben, ist er nicht gutmütig gibt es
keine Chance ihm diese zu entziehen
(4) Zyklische Wartesituation: Die Teilnehmer können in
eine zyklische Abhängigkeitssituation kommen.
© Prof. Dr. Nikolaus Wulff
Informatik III
14
Vier Strategien gegen Verklemmung
• Vorbeugen
– Durch Einführen einer Hierarchie und Vorgabe diese immer
in gleicher Reihenfolge anzufordern wird (4) durchbrochen.
• Vermeiden
– Einem Teilnehmer wird verboten eine Ressource zu
belegen, nur um anschließend zu merken das noch eine
weitere fehlt. Dadurch wird (2) durchbrochen.
• Entdecken
– Analyse ob es zur Situation (4) kommen kann.
• Auflösen
– Kommt es zu einer Verklemmung wird die Ressource
wieder entzogen und somit (3) durchbrochen.
© Prof. Dr. Nikolaus Wulff
Informatik III
15
Teilnehmer-Ressourcen Graph
• Teilnehmer-Ressourcen Abhängigkeiten lassen sich
als gerichteter Graph präsentieren:
T1
T2
R1
R2
T hält R
T benötigt R
• Graph einer zyklischen Abhängigkeit: T1 hält R1 und
wartet auf R2 während T2 auf R1 wartet und R2 hält.
© Prof. Dr. Nikolaus Wulff
Informatik III
16
Wait-For Graph
• Abstraktion der Ressourcen zeigt die reine
Abhängigkeit der Teilnehmer voneinander:
T1
T2
Tj wartet auf Tk
• An Hand eines solchen WFG Graphen lassen sich
zyklische Abhängigkeiten identifizieren.
© Prof. Dr. Nikolaus Wulff
Informatik III
17
Identifizierung von Zyklen
• Es gibt verschiedene Möglichkeiten einen Zyklus in
einem Wait-For Graphen zu entdecken:
1) Auswertung der Adjazenzmatrix AG und deren
Transitiven Hülle RG. Gibt es in der Diagonalen von RG
Einsen, so liegt eine Verklemmung vor.
2) Rekursive Reduktion des Wait-For Graphen. Bleiben
nach der Reduktion noch Teilnehmer T über so liegt eine
Verklemmung vor.
© Prof. Dr. Nikolaus Wulff
Informatik III
18
Warshall Algorithmus
• Die Bestimmung von RG = ∪ Bk erfordert eine
aufwendige Berechnung aller Potenzen von AG.
• Einfacher ist der Algorithmus nach Warshall (1962):
for(k=0; k<N; k++)
agrk
agkc
for(r=0; r<N; r++)
if( ag[r][k]==true) {
agrc
for(c=0; c<N; c++)
ag[r][c] |= ag[k][c];
}
• Die Elemente agrc der Matrix AG werden als boolsche
Variablen mit Werten {false,true} betrachtet.
© Prof. Dr. Nikolaus Wulff
Informatik III
19
Rekursive Reduktion des WFG
• Der folgende Pseudocode eliminiert rekursiv alle
Teilnehmer Tk die ordnungsgemäß terminieren, d.h.
keine Abhängigkeiten haben und somit ihre
Ressourcen freigeben.
for(; ;) {
find node Tk with no outgoing edges;
if( no such node )
break;
erase Tk and all incoming edges;
}
if (any Tk are left)
there is a deadlock;
© Prof. Dr. Nikolaus Wulff
Informatik III
20
Java Thread Synchronisierung
• Shareable Ressourcen müssen über Thread-Grenzen
hinweg synchronisiert werden.
• In der Java Sprache sind hierzu das synchronizied
Schlüsselwort sowie die zugehörigen Methoden wait,
notify und notifyAll zur Inter-Thread-Kommunikation
in der Klasse Objekt vorgesehen.
• Weitere Möglichkeiten sind seit dem JDK 1.5+ in den
neuen Paketen java.util.concurrent.* zu finden:
– Callable<V> Runnable mit Ergebnis vom Typ V.
– Future<V> Resultat einer asynchronen Berechnung.
– sowie zahlreiche Implementierungen und Hilfsklassen...
© Prof. Dr. Nikolaus Wulff
Informatik III
21
Java Synchonisierungsmechanismen
• Jedes Java Objekt besitzt ein zugehöriges Monitor
Objekt, das von der JVM als Sperre verwendet wird.
• Die Sperre wird per synchronized Schlüsselwort an
dem Objekt gesetzt. Nur ein Thread zur Zeit kann eine
solche Critical Section exklusiv durchlaufen.
public synchronized void foo() {
// begin critical section
doSomething_Critical();
} // end critical section
• Intern wird im Bytecode der JVM ein monitorenter
und monitorexit Opcode erzeugt.
© Prof. Dr. Nikolaus Wulff
Informatik III
22
Java synchronized Block
• Die Sperre ist immer mit dem zugehörigen Java
Objekt verbunden.
• Dies ist bei einer Methode das „this“-Objekt.
• Oder in einem synchronized Block das explizit
angegebene Objekt (this) oder eine Instanz:
public void bar() {
doSomething_noneCritical();
// begin critcal section block
synchronized(anObject) {
doSomething_Critical();
} // end critical section block
doMore_noneCritical();
}
© Prof. Dr. Nikolaus Wulff
Informatik III
23
synchronized und Deadlocks
• Durch synchronized Methoden & Blöcke werden
Race Conditions eleminiert.
• Falls jedoch direkt oder indirekt wait gerufen wird,
z.B. per RessourcePool besteht Deadlock Gefahr.
• Als Beispiel das N-Philosophen Problem:
• N Philosophen sitzen an einem Tisch und denken oder
aber essen. Zum Essen benötigen sie eine Gabel und
einen Löffel. Es gibt aber nur M<N Gedecke.
• Die zwei Ressourcenarten Gabel und Löffel werden
jeweils durch einen ResourcePool<T> synchronisiert.
Dadurch kann es zu einem Deadlock kommen. (Wie?)
© Prof. Dr. Nikolaus Wulff
Informatik III
24
Sequenz der Philosopheninteraktion
• Jeder Philosoph wird als eigenständiger Thread
gestartet, so dass alle unabhängig laufen.
• Um zu Essen müssen nacheinander zwei Ressourcen Gabel und Löffel -, vom Tisch genommen werden.
• Erhält der Philosoph keine von beiden so wartet er.
• Erhält er nur eine Ressource, so blockiert er diese
und wartet auf die Zweite.
• Erst wenn er beide Ressourcen hat isst er und legt
anschließend Gabel und Löffel auf den Tisch zurück,
um wieder zu denken.
• Der nächste blockierte Philosoph wird geweckt...
© Prof. Dr. Nikolaus Wulff
Informatik III
25
Philosophen Runde
Forks und Spoons werden
durch zwei Semaphore
synchronisiert.
• N Philosophen wetteifern um die M Ressourcen.
– Ohne Synchronisierung kommt es zur Race Condition.
– Mit (falscher) Synchronisierung zum Deadlock.
© Prof. Dr. Nikolaus Wulff
Informatik III
26
Philosophen Deadlock
• Bei zufälliger Reihenfolge von Messer/Löffel-Zugriff
kommt es nach einiger Zeit zur Verklemmung. Solche
Fehler sind sehr schwer durch Testen zu entdecken!
© Prof. Dr. Nikolaus Wulff
Informatik III
27
Semaphore & ResourceManger
• Eine Implementierung des ResourcePools wird
synchronisierte Methoden verwenden, um die
Resourcen zu schützen. (Siehe Übung).
public interface ResourcePool<T> {
/**
* Acquire a resource of type T,
* this call might block if none available.
* @return T resource
*/
T acquire();
/**
* Release a previously acquired resource back to the pool.
* @param o T resource to release.
*/
void release(final T o);
}
© Prof. Dr. Nikolaus Wulff
Informatik III
28
Semaphore & wait-notify
• Synchronisierung lässt sich per wait-notify oder
gekapselt per Semaphore erreichen.
volatile int size;
public synchronized void release(T resource) {
size++;
pool.add(resource);
notify();
}
public synchronized T require() {
try {
public void release(T resource) {
while (size == 0) {
pool.add(resource);
wait();
semaphore.release();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
public synchronized T require() {
}
try {
size--;
semaphore.acquire();
return pool.remove(0);
} catch (InterruptedException ex) {
}
ex.printStackTrace();
}
return pool.remove(0);
}
© Prof. Dr. Nikolaus Wulff
Informatik III
29
Achtung
• Ein Semaphor eliminiert Race-Condition. Der Pool
wird einen Client solange blockieren, bis eine freie
Ressource zur Verfügung steht.
Dies generiert leider weitere Probleme:
• Wer garantiert, dass der Client die Ressource in den
Pool zurückgibt?
• Wer verhindert, dass nach der Rückgabe an den Pool
diese Ressource nicht weiterhin vom ursprünglichen
Client verwendet wird?
© Prof. Dr. Nikolaus Wulff
Informatik III
30
Abhilfe
Dies lässt sich mit Hilfe verschiedener Strategien
verhindern:
• Die Referenz wird ungültig nach dem release-Aufruf.
• Die Referenz ist eine bestimmte Zeitspanne gültig.
Danach generiert ihrer Verwendung durch den Client
eine TimeoutException.
• Ein interner GarbageCollector sammelt nicht
zurückgegebene Ressourcen ein und gibt diese in den
Pool zurück. Die Referenz ist ab dann ungültig.
© Prof. Dr. Nikolaus Wulff
Informatik III
31
Problem
• Wenn die Referenz entweder ungültig wird oder einen
Timeout hervorruft lohnt es sich nicht diese in einem
Pool zu cachen...
Lösung:
• Der Pool gibt keine „echte Referenz“ an den Client
heraus, sondern ein Stellvertreterobjekt, ein Proxy.
• Dieses Proxy hat dieselbe Schnittstelle wie die echte
Referenz, kann aber durch den Pool administriert
werden, um invalide oder per timeout disabled oder
garbage-collected zu werden.
© Prof. Dr. Nikolaus Wulff
Informatik III
32
ResourcePool Proxy
© Prof. Dr. Nikolaus Wulff
Informatik III
33
Prinzip des Proxy
• Ressource und Proxy implementieren die selbe
Schnittstelle <T>.
• Jeder Methodenaufruf wird vom Proxy an die
Reference weiter delegiert:
public int foo() {
return getReference().foo();
}
• Die getReference Method bietet die Möglichkeit zu
überprüfen, ob die Reference noch gültig ist!
public T getReference() {
checkAccess();
return super.getReference();
}
© Prof. Dr. Nikolaus Wulff
Informatik III
34
Resource Recycling
• Der ResourcePool invalidiert ein Proxy wenn es per
release zurückgegeben wird. Ab dann ist die
Ressource mit diesem Proxy nicht mehr erreichbar, da
jeder Aufruf zu einer Exception beim Client führt.
• Sollte das Proxy nicht zurückgegeben werden, so hat
dies einen Timestamp für seine Lebensdauer, danach
führt jede weitere Verwendung zu einer Exception.
• Der ResourcePool hat einen Garbage-Collector
Thread, der alle herausgegebenen Proxys nach einer
vorgegebener Zeit invalidiert und die Resourcen in
den Pool zurückgibt. So werden sie dem Client
effektiv entzogen falls er den release-Aufruf vergißt...
© Prof. Dr. Nikolaus Wulff
Informatik III
35
Timeout
• Überprüfung der Lebensdauer:
/**
* check that the access is within the
* time frame, otherwise an exception
* will be thrown.
*/
private void checkAccessTime() {
long now = System.currentTimeMillis();
if (now>endTime) {
String msg = String.format("time excess: %d mSec delay",
now-endTime);
throw new TimerException(msg);
}
}
• Wird dies in der getReference() Methode aufgerufen,
so ist das Proxy nach dem Timeout für den Client
effektiv nicht mehr zu gebrauchen...
© Prof. Dr. Nikolaus Wulff
Informatik III
36
Proxy Vor- und Nachteile
• Mit Hilfe des Proxy Ansatz lassen sich Resourcen
effektiv und sicher wieder an den ResourcePool
zurückgeben.
• Ein Nachteil ist, dass hierzu eine gemeinsame
Schnittstelle vorliegen muss, oder die Proxys müssen
von der Ressource ableitbar sein. (Dann sind es keine
eigentlichen Proxys mehr, sondern Dekorierer...)
• C++ gestattet es die Referenzierungsoperatoren „->“
und „*“ sowie den Zuweisungsoperator „=“ zu
überladen, so dass mit einem „intelligenten Zeiger“
eine effektive Überprüfung der Zugriffe erfolgt.
© Prof. Dr. Nikolaus Wulff
Informatik III
37
C++ Handle
template <class T> class Handle {
private: T* ptr;
public:
Handle() :ptr(0) { }
Handle(T* ref) :ptr(ref) { assert(ptr != 0); }
~Handle() { if(ptr) delete ptr; ptr=0;}
// assignment
Handle& operator=(const Handle&);
// pointer access
T& operator*() const;
T* operator->() const;
operator bool() const {return ptr!=0;}
// invalidate the handle
void invalidate() { ptr = 0;}
};
© Prof. Dr. Nikolaus Wulff
Informatik III
38
Daumenregeln gegen Deadlocks
• Lokale Methodenvariablen sind immer Thread-sicher
und benötigen keine Synchronisierungsmechanismen.
• Globale read-only Variablen sind Thread-sicher
– also entweder final primitive Attribute
– oder Objekte, die selber wiederum nicht veränderbar sind.
• Synchronisierte Methoden vermeiden oder aber
sicherstellen, das diese immer! in der selben
Reihenfolge Lock-Objekte alloziieren.
– letzte Bedingung erfordert Analyse des WFG.
– => UML Sequenzdiagramm kann helfen
© Prof. Dr. Nikolaus Wulff
Informatik III
39
Literatur
• „Java Threads“, S. Oaks und H. Wong, O'Reilly, 1997
– Das gesamte Buch handelt von Konzepten zur Thread
Synchronisierung.
• „Threads und Netzwerk-Programmierung mit Java“,
H. Kredel und A. Yoshida, dpunkt, 1999
– Konzepte zur Parallelverarbeitung für Netzwerk- und
Thread-Programmierung.
• „Inside the Virtual Machine“, B. Venners, McGrawHill, 1998.
– Kapitel 20 erläutert die Critical Section innerhalb der JVM
• „Die Programmiersprache Java“, K. Arnold und J.
Gosling, Addison-Wesley, 1996
© Prof. Dr. Nikolaus Wulff
Informatik III
40
Herunterladen