Titel | Inhalt | Suchen | Index | DOC | Handbuch der Java-Programmierung, 3. Auflage
<< | < | > | >> | API | Kapitel 22 - Multithreading

## 22.2 Die Klasse Thread

- 22.2 Die Klasse Thread
  - 22.2.1 Erzeugen eines neuen Threads
  - 22.2.2 Abbrechen eines Threads
  - 22.2.3 Anhalten eines Threads
  - 22.2.4 Weitere Methoden
    - sleep
    - isAlive
    - join

### 22.2.1 Erzeugen eines neuen Threads

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, muß 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. |  |
Warnung

Das folgende Beispiel zeigt einen einfachen Thread, der in einer Endlosschleife einen Zahlenwert hochzählt:

    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

Listing 22.1: Ein einfacher Thread mit einem Zähler

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. |  |
Hinweis

### 22.2.2 Abbrechen eines Threads

Zunächst einmal wird ein Thread dadurch beendet, daß 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

Listing 22.2: Beenden des Threads durch Aufruf von stop

An diesem Beispiel kann man gut erkennen, daß 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, daß 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. |  |
JDK1.1-1.4

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, daß 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 festellen, 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

Listing 22.3: Anwendung der Methoden interrupt und isInterrupted

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, daß 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, daß 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, daß 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. |  |
Hinweis

### 22.2.3 Anhalten eines Threads

|  |  |  | 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 muß - wenn erforderlich - manuell nachgebildet werden. |  |
JDK1.1-1.4

### 22.2.4 Weitere Methoden

#### sleep

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, daß der aktuelle Prozeß 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. |  |
Hinweis

Als Klassenmethode kann `sleep` aufgerufen werden, ohne daß 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.

#### isAlive

Mit dieser Methode kann festgestellt werden, ob der aktuelle Thread noch läuft.

    public final boolean isAlive()
| java.lang.Thread

`isAlive` gibt immer dann `true` zurück, wenn der aktuelle 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.

#### join

    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 Prozeß zu starten und (ähnlich einem Funktionsaufruf) mit der weiteren Ausführung so lange zu warten, bis der Prozeß 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 Prozeß noch nicht beendet ist.

Titel | Inhalt | Suchen | Index | DOC | Handbuch der Java-Programmierung, 3. Auflage, Addison Wesley, Version 3.0
<< | < | > | >> | API | © 1998-2002 Guido Krüger, http://www.javabuch.de