Titel   Inhalt   Suchen   Index   API  Go To Java 2, Zweite Auflage, Handbuch der Java-Programmierung
 <<    <     >    >>  Kapitel 22 - Multithreading

22.2 Die Klasse Thread



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 MyThread 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.3 

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

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 Fall von MS-DOS/Windows entspricht sie in der Regel der Genauigkeit des System-Tickers, liegt also bei etwa 55 ms; unter NT beträgt sie etwa 10 ms.

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   API  Go To Java 2, Zweite Auflage, Addison Wesley, Version 2.0
 <<    <     >    >>  © 2000 Guido Krüger, http://www.gkrueger.com