From 33613a85afc4b1481367fbe92a17ee59c240250b Mon Sep 17 00:00:00 2001
From: Sven Eisenhauer
+Die Klasse Thread
+ist Bestandteil des Pakets java.lang
+und steht damit allen Anwendungen standardmäßig zur Verfügung.
+Thread
+stellt die Basismethoden zur Erzeugung, Kontrolle und zum Beenden
+von Threads zur Verfügung. Um einen konkreten Thread zu erzeugen,
+muss eine eigene Klasse aus Thread
+abgeleitet und die Methode run
+überlagert werden.
+
+
+Mit Hilfe eines Aufrufs der Methode start
+wird der Thread gestartet und die weitere Ausführung an die Methode
+run
+übertragen. start
+wird nach dem Starten des Threads beendet, und der Aufrufer kann parallel
+zum neu erzeugten Thread fortfahren.
+
+
+Die Methode run
+sollte vom Programm niemals direkt aufgerufen werden. Um einen Thread
+zu starten, ist immer start
+aufzurufen. Dadurch wird der neue Thread erzeugt und initialisiert
+und ruft schließlich selbst run
+auf, um den Anwendungscode auszuführen. Ein direkter Aufruf von
+run
+würde dagegen keinen neuen Thread erzeugen, sondern wäre
+ein normaler Methodenaufruf wie jeder andere und würde direkt
+aus dem bereits laufenden Thread des Aufrufers erfolgen.
+Das folgende Beispiel zeigt einen einfachen Thread, der in einer Endlosschleife
+einen Zahlenwert hochzählt:
+
+
+
+
+
+
+ Titel
+ Inhalt
+ Suchen
+ Index
+ DOC
+ Handbuch der Java-Programmierung, 5. Auflage
+
+ <<
+ <
+ >
+ >>
+ API
+ Kapitel 22 - Multithreading
+
+
+
+
+
+22.2 Die Klasse Thread
+
+
+
+
+
+
+
+22.2.1 Erzeugen eines neuen Threads
+
+
+
+
+
+
+
+
+![]()
+
+![]()
+
+
+
+![]()
+
+
+
+
+
+ Warnung
+
+
+
+
+Listing 22.1: Ein einfacher Thread mit einem Zähler
+
+
+
+
+
+001 /* Listing2201.java */
+002
+003 class MyThread2201
+004 extends Thread
+005 {
+006 public void run()
+007 {
+008 int i = 0;
+009 while (true) {
+010 System.out.println(i++);
+011 }
+012 }
+013 }
+014
+015 public class Listing2201
+016 {
+017 public static void main(String[] args)
+018 {
+019 MyThread2201 t = new MyThread2201();
+020 t.start();
+021 }
+022 }
+
+
+Listing2201.java
+
+Zunächst wird hier ein neues Objekt vom Typ MyThread2201 +instanziert. Die Ausführung eines Threads ist damit vorbereitet, +aber noch nicht tatsächlich erfolgt. Erst durch den Aufruf von +start +wird ein neuer Thread erzeugt und durch einen impliziten Aufruf von +run +der Thread-Body gestartet. Da das Programm in einer Endlosschleife +läuft, läßt es sich nur gewaltsam abbrechen (beispielsweise +durch Drücken von [STRG]+[C]). +
+
![]() |
+
+
+ +Im Gegensatz zu unseren bisherigen Beispielen wird dieses Programm +nicht automatisch nach main +beendet. Eine Java-Applikation +wird nämlich immer nach Ende des letzten Threads beendet, der +kein Hintergrund-Thread (Dämon) +ist. Da ein einfaches Programm nur einen einzigen Vordergrund-Thread +besitzt (nämlich den, in dem main +läuft), wird es demnach beendet, wenn main +beendet wird. Das Beispielprogramm erzeugt dagegen einen zusätzlichen +Vordergrund-Thread und kann damit vom Interpreter erst dann beendet +werden, wenn auch dieser Thread beendet wurde. Durch Aufruf von exit +lassen sich auch Programme mit laufenden Vordergrund-Threads abbrechen. |
+
+
|
+![]() |
+
+Zunächst einmal wird ein Thread dadurch beendet, dass das Ende +seiner run-Methode +erreicht ist. In manchen Fällen ist es jedoch erforderlich, den +Thread von außen abzubrechen. Die bis zum JDK 1.1 übliche +Vorgehensweise bestand darin, die Methode stop +der Klasse Thread +aufzurufen. Dadurch wurde der Thread abgebrochen und aus der Liste +der aktiven Threads entfernt. + +
+Wir wollen das vorige Beispiel erweitern und den Thread nach zwei +Sekunden durch Aufruf von stop +beenden: + + +
+
+
+
+001 /* Listing2202.java */
+002
+003 class MyThread2202
+004 extends Thread
+005 {
+006 public void run()
+007 {
+008 int i = 0;
+009 while (true) {
+010 System.out.println(i++);
+011 }
+012 }
+013 }
+014
+015 public class Listing2202
+016 {
+017 public static void main(String[] args)
+018 {
+019 MyThread2202 t = new MyThread2202();
+020 t.start();
+021 try {
+022 Thread.sleep(2000);
+023 } catch (InterruptedException e) {
+024 //nichts
+025 }
+026 t.stop();
+027 }
+028 }
+
+ |
++Listing2202.java | +
+An diesem Beispiel kann man gut erkennen, dass der Thread tatsächlich +parallel zum Hauptprogramm ausgeführt wird. Nach dem Aufruf von +start +beginnt einerseits die Zählschleife mit der Bildschirmausgabe, +aber gleichzeitig fährt das Hauptprogramm mit dem Aufruf der +sleep-Methode +und dem Aufruf von stop +fort. Beide Programmteile laufen also parallel ab. +
+
![]() |
+![]() |
+
+
+ +Mit dem JDK 1.2 wurde die Methode stop +als deprecated +markiert, d.h. sie sollte nicht mehr verwendet werden. Der Grund dafür +liegt in der potentiellen Unsicherheit des Aufrufs, denn es +ist nicht voraussagbar und auch nicht definiert, an welcher Stelle +ein Thread unterbrochen wird, wenn ein Aufruf von stop +erfolgt. Es kann nämlich insbesondere vorkommen, dass der Abbruch +innerhalb eines kritischen Abschnitts erfolgt (der mit dem synchronized-Schlüsselwort +geschützt wurde) oder in einer anwendungsspezifischen Transaktion +auftritt, die aus Konsistenzgründen nicht unterbrochen werden +darf. |
+
+
|
+![]() |
+
+Die alternative Methode, einen Thread abzubrechen, besteht darin, +im Thread selbst auf Unterbrechungsanforderungen zu reagieren. So +könnte beispielsweise eine Membervariable cancelled +eingeführt und beim Initialisieren des Threads auf false +gesetzt werden. Mit Hilfe einer Methode cancel +kann der Wert der Variable zu einem beliebigen Zeitpunkt auf true +gesetzt werden. Aufgabe der Bearbeitungsroutine in run +ist es nun, an geeigneten Stellen diese Variable abzufragen und für +den Fall, dass sie true +ist, die Methode run +konsistent zu beenden. + +
+Dabei darf cancelled natürlich +nicht zu oft abgefragt werden, um das Programm nicht unnötig +aufzublähen und das Laufzeitverhalten des Threads nicht zu sehr +zu verschlechtern. Andererseits darf die Abfrage nicht zu selten erfolgen, +damit es nicht zu lange dauert, bis auf eine Abbruchanforderung reagiert +wird. Insbesondere darf es keine potentiellen Endlosschleifen geben, +in denen cancelled überhaupt +nicht abgefragt wird. Die Kunst besteht darin, diese gegensätzlichen +Anforderungen sinnvoll zu vereinen. + +
+Glücklicherweise gibt es in der Klasse Thread bereits einige +Methoden, die einen solchen Mechanismus standardmäßig unterstützen: +
+
+
++public void interrupt() + +public boolean isInterrupted() + +public static boolean interrupted() ++ + |
++java.lang.Thread | +
+Durch Aufruf von interrupt +wird ein Flag gesetzt, das eine Unterbrechungsanforderung signalisiert. +Durch Aufruf von isInterrupted +kann der Thread feststellen, ob das Abbruchflag gesetzt wurde und +der Thread beendet werden soll. Die statische Methode interrupted +stellt den Status des Abbruchsflags beim aktuellen Thread fest. +Ihr Aufruf entspricht dem Aufruf von currentThread().isInterrupted(), +setzt aber zusätzlich das Abbruchflag auf seinen initialen Wert +false +zurück. + +
+Wir wollen uns den Gebrauch dieser Methoden an einem Beispiel ansehen. +Dazu soll ein Programm geschrieben werden, das in einem separaten +Thread ununterbrochen Textzeilen auf dem Bildschirm ausgibt. Das Hauptprogramm +soll den Thread erzeugen und nach 2 Sekunden durch einen Aufruf von +interrupt +eine Unterbrechungsanforderung erzeugen. Der Thread soll dann die +aktuelle Zeile fertig ausgeben und anschließend terminieren. + + +
+
+
+
+001 /* Listing2203.java */
+002
+003 public class Listing2203
+004 extends Thread
+005 {
+006 int cnt = 0;
+007
+008 public void run()
+009 {
+010 while (true) {
+011 if (isInterrupted()) {
+012 break;
+013 }
+014 printLine(++cnt);
+015 }
+016 }
+017
+018 private void printLine(int cnt)
+019 {
+020 //Zeile ausgeben
+021 System.out.print(cnt + ": ");
+022 for (int i = 0; i < 30; ++i) {
+023 System.out.print(i == cnt % 30 ? "* " : ". ");
+024 }
+025 System.out.println();
+026 //100 ms. warten
+027 try {
+028 Thread.sleep(100);
+029 } catch (InterruptedException e) {
+030 interrupt();
+031 }
+032 }
+033
+034 public static void main(String[] args)
+035 {
+036 Listing2203 th = new Listing2203();
+037 {
+038 //Thread starten
+039 th.start();
+040 //2 Sekunden warten
+041 try {
+042 Thread.sleep(2000);
+043 } catch (InterruptedException e) {
+044 }
+045 //Thread unterbrechen
+046 th.interrupt();
+047 }
+048 }
+049 }
+
+ |
++Listing2203.java | +
+Die main-Methode +ist leicht zu verstehen. Sie startet den Thread, wartet 2 Sekunden +und ruft dann die Methode interrupt +auf. In der Methode run +wird in einer Endlosschleife durch Aufruf von printLine +jeweils eine neue Zeile ausgegeben. Zuvor wird bei jedem Aufruf mit +isInterrupted +geprüft, ob das Abbruchflag gesetzt wurde. Ist das der Fall, +wird keine weitere Zeile ausgegeben, sondern die Schleife (und mit +ihr der Thread) beendet. + +
+Innerhalb von printLine wird +zunächst die Textzeile ausgegeben und dann eine Pause von 100 +Millisekunden eingelegt. Da in der Methode keine Abfrage des Abbruchflags +erfolgt, ist sichergestellt, dass die aktuelle Zeile selbst dann bis +zum Ende ausgegeben wird, wenn der Aufruf von interrupt +mitten in der Schleife zur Ausgabe der Bildschirmzeile erfolgt. + +
+Da die Pause nach der Bildschirmausgabe mit 100 Millisekunden vermutlich +länger dauert als die Bildschirmausgabe selbst, ist es recht +wahrscheinlich, dass der Aufruf von interrupt +während des Aufrufs von sleep +erfolgt. Ist das der Fall, wird sleep +mit einer InterruptedException +abgebrochen (auch wenn die geforderte Zeitspanne noch nicht vollständig +verstrichen ist). Wichtig ist hier, dass das Abbruchflag zurückgesetzt +wird und der Aufruf von interrupt +somit eigentlich verlorengehen würde, wenn er nicht direkt in +der catch-Klausel +behandelt würde. Wir rufen daher innerhalb der catch-Klausel +interrupt +erneut auf, um das Flag wieder zu setzen und run +die Abbruchanforderung zu signalisieren. Alternativ hätten wir +auch die Ausnahme an den Aufrufer weitergeben können und sie +dort als Auslöser für das Ende der Ausgabeschleife betrachten +können. +
+
![]() |
+
+
+ +Die beiden anderen Methoden, die eine Ausnahme des Typs InterruptedException +auslösen können, sind join +der Klasse Thread +und wait +der Klasse Object. +Auch sie setzen beim Auftreten der Ausnahme das Abbruchflag zurück +und müssen daher in ähnlicher Weise behandelt werden. |
+
+
|
+![]() |
+
+
![]() |
+![]() |
+
+
+ +Die Klasse Thread +besitzt zwei Methoden suspend +und resume, +mit deren Hilfe es möglich ist, einen Thread vorübergehend +anzuhalten und anschließend an der Unterbrechungsstelle fortzusetzen. +Beide Methoden sind nicht ganz ungefährlich und können unbemerkt +Deadlocks verursachen. Sie wurden daher mit dem JDK 1.2 als deprecated +markiert und sollten nicht mehr verwendet werden. Ihre Funktionalität +muss - wenn erforderlich - manuell nachgebildet werden. |
+
+
|
+![]() |
+
+Sowohl innerhalb der Threads als auch innerhalb der Methode main +wird ein Aufruf von Thread.sleep +verwendet, um das Programm pausieren zu lassen. sleep +ist eine statische Methode der Klasse Thread, +die mit einem oder zwei Parametern aufgerufen werden kann: +
+
+
++public static void sleep(long millis) +public static void sleep(long millis, int nanos) ++ + |
++java.lang.Thread | +
+Die erste Version sorgt dafür, dass der aktuelle prozess für +die (in Millisekunden angegebene) Zeit unterbrochen wird. Die zweite +erlaubt eine noch genauere Eingabe der Wartezeit, indem auch Bruchteile +im Nanosekundenbereich angegeben werden können. In beiden Fällen +wird die tatsächlich erzielbare Genauigkeit allerdings durch +Hardwarerestriktionen der Zielmaschine begrenzt. Im Falle der aktuellen +SUN-JDKs unter Windows liegt sie bei etwa 1 ms und ist damit wesentlich +höher als currentTimeMillis. +In Abschnitt 16.3.5 finden +sich Beispielprogramme, mit denen die Auflösungen von currentTimeMillis +und sleep +ermittelt werden können. +
+
![]() |
+
+
+ +Die Kapselung des Aufrufs von Thread.sleep +innerhalb eines try-catch-Blocks +ist erforderlich, weil sleep +während der Wartezeit eine Ausnahme vom Typ InterruptedException +auslösen kann. Ohne den try-catch-Block +würde diese an den Aufrufer weitergegeben werden. |
+
+
|
+![]() |
+
+Als Klassenmethode kann sleep +aufgerufen werden, ohne dass eine Instanz der Klasse Thread +verfügbar ist. Insbesondere kann die Methode auch dazu verwendet +werden, das Hauptprogramm pausieren zu lassen, das ja nicht explizit +als Thread erzeugt wurde. Dies funktioniert deshalb, weil beim Starten +eines Java-Programms automatisch ein Thread für die Ausführung +des Hauptprogramms angelegt wurde. + + + + +
+Mit dieser Methode kann festgestellt werden, ob ein Thread noch läuft. +
+
+
++public final boolean isAlive() ++ + |
++java.lang.Thread | +
+isAlive +gibt immer dann true +zurück, wenn der Thread gestartet, aber noch nicht wieder beendet +wurde. Beendet wird ein Thread, wenn das Ende der run-Methode +erreicht ist oder wenn (in Prä-1.2-JDKs) die Methode stop +aufgerufen wurde. + + + + +
+
+
++public final void join() + throws InterruptedException ++ + |
++java.lang.Thread | +
+Die Methode join +wartet auf das Ende des Threads, für den sie aufgerufen wurde. +Sie ermöglicht es damit, einen prozess zu starten und (ähnlich +einem Funktionsaufruf) mit der weiteren Ausführung so lange zu +warten, bis der prozess beendet ist. join +gibt es auch mit einem long +als Parameter. In diesem Fall wartet die Methode maximal die angegebene +Zeit in Millisekunden und fährt nach Ablauf der Zeit auch dann +fort, wenn der prozess noch nicht beendet ist. +
| Titel + | Inhalt + | Suchen + | Index + | DOC + | Handbuch der Java-Programmierung, 5. Auflage, Addison +Wesley, Version 5.0.1 + |
| << + | < + | > + | >> + | API + | © 1998, 2007 Guido Krüger & Thomas +Stark, http://www.javabuch.de + |