10 Threads
Inhalt
Threads ·Zustände von Threads ·Synchronisation ·Prioritäten ·Threadgroups
Literatur:
[Fla99b] gibt diesmal nicht soviel her, zumindest
nicht auf einem Fleck konzentriert. In [LL97] werden
Threads in Abschnitt 14.2 besprochen. Der entsprechende Abschnitt in Suns
Javatutorial [CW96] ist ebenfalls recht
brauchbar. Daneben gibt es natürlich noch [OW97]
nur über Threads in Java, aber ich kenne es nicht selbst. Auch in
[Lea99] findet sich eine sehr empfehlenswerte Beschreibung
von Synchronisation in Java, auch in einem größeren Kontext
(insbesondere Abschnitt 2.2 für Locking). In der Klassenbibliothek sind
java.lang.Thread und java.lang.ThreadGroup die
wichtigsten Klassen zum Thema.
- Definition:
Thread: ein sequentieller Kontrollfluß
innerhalb eines Programmes
|
- bezeichnet auch als lightweight processes
- quasi-parallele Ausführung mehrerer sequentieller
Kontrollfüsse ( Multi-Threaded)
- Bisher: Ein (Anwender)-Programm/Applet = ein Thread (single
threaded), aber:
- im Laufzeitsystem: Threads, die von der Java
Virtual Machine automatisch erzeugt und verwaltet werden:
Dämonen
-
Garbage Collection: niederpriorer Hintergrundthread
- Threads für das Event-Handling
- ein Thread bedient die main-Methode
- Programmterminierung: alle nicht-Dämonenthreads sind beendet
- Thread: Verwaltung von threads: starten, stoppen,
Prioritäten setzen ...
- Thread kann eine best. Priorität haben
-
Klasse java.lang.Thread, muß also nicht importiert werden
- Thread implementiert Runnable
- zwei Arten, Threads zu generieren:
t]15cm
-
als Unterklasse der Thread-Klasse und
überschreiben der run-Methode, oder
- Implementierung des Runnable-Interface.
|
- run() ist der Rumpf der Threads
- Der Thread-Konstruktor nimmt die Implementierung des
runnable-Objektes als Argument58
- Ein neuer Thread wird | wie üblich | mit new erzeugt und
durch Aufruf von start gestarted.
- Thread endet, wenn das Ende der
run-Methode erreicht wird.
- Mit suspend und resume kann die Verarbeitung eines threads
angehalten/fortgesetzt werden.
Beispiel 23 [ Unterklasse von Thread]
Die Klasse erweitert einfach Threads und überschreibt
die run-Methode.
public class SimpleThread extends Thread {
public SimpleThread(String str) {
super(str);
}
public void run() { // "uberschreiben
for (int i = 0; i < 10; i++) {
System.out.println(i + " " + getName());
try {
sleep((int)(Math.random() * 1000));
} catch (InterruptedException e) {}
}
System.out.println("DONE! " + getName());
}
};
Beispiel 24 [ Implementieren von Runnable]
Dies ist die zweite Möglichkeit, wie man einen Thread bekommen kann.
Der Konstruktor der Thread-Klasse macht aus einem
Objekt, das ein Runnable-Interface implementiert, den
eigentlichen Thread. Dieser muß dann extra gestartet werden.
Das starten wiederum ruft run auf.
import java.awt.Graphics;
import java.util.*;
import java.text.DateFormat;
import java.applet.Applet;
public class Clock extends Applet implements Runnable {
private Thread clockThread = null;
public void start() {
if (clockThread == null) {
clockThread = new Thread(this, "Clock"); // Konstuktor Thread
clockThread.start();
}
}
public void run() { // wg. Interface runnable
Thread myThread = Thread.currentThread();
while (clockThread == myThread) {
repaint();
try {
Thread.sleep(1000);
} catch (InterruptedException e){}
}
}
public void paint(Graphics g) {
Calendar cal = Calendar.getInstance();
Date date = cal.getTime();
DateFormat dateFormatter = DateFormat.getTimeInstance();
g.drawString(dateFormatter.format(date), 5, 10);
}
public void stop() { // Applets stop Methode
clockThread = null;
}
};
-
neu: bereits instantiiert, aber noch nicht gestartet
- ausführbar
- tot: am Ende der run-Methode. Oder auch nach
Aufruf von stop, wird aber in Java-1.2 missbilligt
- blockiert/nicht ausführbar. Mehrere Gründe
-
schlafend: feste Zeitspanne
- suspendiert: keine Reaktion bis resume von
außen aufgerufen wird
- wartend: keine Reaktion bis notify
- Aufruf einer synchronisierten Methode an einem Objekt,
welches gerade anderweitig beschäftigt (locked) ist
- Warten auf Beendigung von I/O, beispielsweise ein
Strom
Lebenszyklus eines Threads
|
Hier eine Zusammenfassung der Zustände und Übergänge von
Threads:
-
Quasi-paralleler Zugriff auf gemeinsame Daten Þ
notwendig
- Kritischer Abschnitt: Programmabschnitt, für den
gegenseitiger Ausschluß (also exklusiver Zugriff) erforderlich
ist.
Beispiel 25 [Leser/Schreiber]
In folgendem Scenario haben die Objekte Schreiber und Leser Zugriff auf
dasselbe Speicherobjekt, der eine schreibend, der andere lesend. Das
Speicherobjekt soll einfach eine Speicherzelle darstellen, auf
die mit den Methoden get und put zugegriffen wird.
Es gibt zwei Dinge zu koordinieren:
-
gegenseitiger Ausschluss: locking
- Unterschiedliche Geschwindigkeiten von Leser und Schreiber
( race conditions): der Zugriff muss abgestimmt werden.
Gegenseitiger Ausschluss (synchronized)
|
Beispiel 26 [Leser/Schreiber (2)]
Da im Leser/Schreiber-Beispiel mittels der beiden
Zugriffsmethoden get und put auf die
gemeinsamen Daten zugegriffen werden soll, kann man den gleichzeitigen
Zugriff dadurch verbieten, dass man die Methoden als
synchronized markiert.
public class Speicher { // Speicher muß kein Thread sein
private int speicherzelle // das gemeinsame Datum
public synchronized int get () {
....
};
public synchronized void put (int value) {
....
};
};
-
nicht nur Verhindern von gleichzeitigem Zugriff, sondern auch
explizites
-
Warten auf das Eintreten von Ereignissen (genauer eine
Benachrichtigung) und
- Benachrichtigen von deren Eintreten
notwendig
- Instanzmethoden in java.lang.Object
-
wait: Warten auf Benachrichtigung
- drei Formen, zwei mit Timeout
-
wait()
- wait (long timeout)
- wait (long timeout, int nanoseconds)
- notify: Benachrichtigung einer bestimmten Instanz
- notifyAll: alle benachrichtigen
- Vorsicht vor Deadlocks durch zyklisches
Warten.
- Die erwähnten Methoden sind nur innerhalb von
synchronisierten Blocks oder Methoden erlaubt.
- wichtiger Unterschied zu
suspend/ resume: Im Wartezustand
gibt der Thread den Lock frei.
Beispiel 27 [Leser/Schreiber (3)]
In dem Leser/Schreiber-Beispiel kann man diese Methoden verwenden, um die
Geschwindigkeit der Threads anzugleichen.
public synchronized int get() {
while (available == false) { // Explizite Schleife
try {
wait(); // Warten auf den Schreiber
} catch (InterruptedException e) {
}
}
available = false;
notifyAll(); // Benachrichtigen
return contents;
}
public synchronized void put(int value) {
while (available == true) {
try {
wait(); // Warten auf den Leser
} catch (InterruptedException e) {
}
}
contents = value;
available = true;
notifyAll(); // Benachrichtigen
}
Sonstige Interaktion mit Threads
|
-
sleep(long milliseconds)
-
für festgelegte Zeitspanne deaktivieren/pausieren
- Klassenmethode der Klasse Thread
- Verwendung oft einfach Thread.sleep(100)
- join(): Instanzmethode, warten auf den Tod
eines Threads
- Mißbilligte Methoden (ab Java 1.2
deprecated)
-
suspend/resume: zweitweises Anhalten und
Weitermachen
-
ein suspendiertes Objekt gibt (anders als ein wartendes)
seine Locks nicht frei Þ
- führt leicht zu Verklemmungen
- stop: Abbrechen
-
gibt alle Locks frei Þ
- Gefahr von inkonsistentem Zustand
- wirft eine Ausnahme,
und zwar eine Unterklasse von Error Þfehlerhafter Abbruch
- interrupt: unterbrechen
- destroy: wurde (noch) nie implementiert, ein
suspend ohne resume.60
Alternative zum Suspendieren
|
Beispiel 28 [Suspendieren]
Folgendes Beispiel zeigt, wie man die Methoden suspend und
resume umgehen kann, indem man wait und notify
und ein Flag (hier suspended verwendet. An Stelle
von suspend und resume treten Methoden
mySuspend und myResume. Das Flag schaltet den
suspend-Zustand ein und aus. Das wait muß
synchronisiert sein.
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
public class Suspend extends Applet {
private TextField t = new TextField(10); // output
private Button // zwei Kn"opfe
suspendbutton = new Button("Suspend"),
resumebutton = new Button("Resume");
public class Suspendable extends Thread {
private int count =0;
private boolean suspended = false; // Flag
public Suspendable (){ start();};
public void mySuspend() {
suspended = true; // Flag => true
};
public synchronized void myResume(){
suspended = false; // Flag => false
notify();
};
public void run() { // Runnable
while (true) {
try {
sleep(100);
synchronized(this) { // auf sich selbst
while (suspended) wait(); // sync-Block
}
} catch (InterruptedException e){};
t.setText(Integer.toString(count++));
};
};
};
private Suspendable ss = new Suspendable();
public void init() { // Applet
add(t);
suspendbutton.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
ss.mySuspend();
}
});
resumebutton.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
ss.myResume();
}
});
add(suspendbutton); // Kn"opfe ans
add(resumebutton); // Applet heften
}; // Ende init
public static void main(String[] args) { // stand-alone
Suspend applet = new Suspend();
Frame aFrame = new Frame("Suspend");
aFrame.addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent e){
System.exit(0); // ordentlich
}; // terminieren
});
aFrame.add(applet, BorderLayout.CENTER);
aFrame.setSize(300, 300);
applet.init();
applet.start(); // thread starten
aFrame.setVisible(true); // = show
};
};
-
quasi-parallele Ausführung von mehreren Threads Þ Scheduler
- Javas Laufzeitsystem: Scheduling mit fester
Priorität61 (fixed priority scheduling)
-
mehrere Threads ausführbar: nimm den
dringlichsten62
- unterbrechend: es läuft immer63 der ausführbare Thread mit der
höchsten Priorität, solange bis er
-
endet64
- ein Ergebnis liefert (yield) oder sonstwie
- nicht-laufend (non-runnable) wird
- ein dringlicherer Thread ausführbar wird.
- Bei Gleichstand: zyklische, nicht-interbreachende
Auswahl. (round robin)
- Manche Betriebssysteme (nicht Javas Laufzeitsystem)
erzwingen Time-slicing, aber: nicht darauf verlassen
(Platformunabhängigkeit)
- Thread erbt die Priorität seines Erzeugers
- Verändern der Priorität: setPriority, Lesen
mit getPriority
- Verhindern von Aushungern:
-
yield: statische Methode: Thread gibt
die Kontrolle auf, lässt anderen gleichprioren Thread den
Vortritt
-
Klasse java.lang. ThreadGroup.
- Jeder Thread gehört zu einer Gruppe (notfalls der
System-Defaultgruppe)
- Gruppen von zusammengehörenden Threads
- statische Gruppenzugehörigkeit
- Operationen auf der Gruppe als Ganzem
-
Informationen über alle Threads der Gruppe sammeln
- Gruppe selbst hat Attribute (z.B. maximale Priorität)
- Interaktion mit allen Threads der Gruppe auf einmal
(alle auf einmal starten etc.)
- Zugriffsbeschränkungen, basierend auf
Gruppenzugehörigkeit (SecurityManager)
- Hierarchie von Thread-Gruppen
?
July 4, 2000