Tit   Inh   Ind   1   2   3   4   5   6   7   8   9   10   11   12   13   14   15   16   17   18   19   20   21   22   23   24   25   26   27   28   29   30   31   32   <<   <   >   >> 

10.2 Die Klasse Thread



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

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

001 /* Listing1001.java */
002 
003 class MyThread1001
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 Listing1001
016 {
017    public static void main(String[] args)
018    {
019       MyThread1001 t = new MyThread1001();
020       t.start();
021    }
022 }
Listing1001.java
Listing 10.1: Ein einfacher Thread mit einem Zähler

 Beispiel 

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 beendet, nachdem main beendet wurde. Es gibt eine einfache Regel, die besagt, daß eine Java-Applikation immer dann beendet wird, wenn der letzte Thread beendet wurde, 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 dieser Thread beendet wurde.

 Hinweis 

10.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 /* Listing1002.java */
002 
003 class MyThread1002
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 Listing1002
016 {
017    public static void main(String[] args)
018    {
019       MyThread1002 t = new MyThread1002();
020       t.start();
021       try {
022         Thread.sleep(2000);
023       } catch (InterruptedException e) {
024         //nichts
025       }
026       t.stop();
027    }
028 }
Listing1002.java
Listing 10.2: Beenden des Threads durch Aufruf von stop

 Beispiel 

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.

Im 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.2 

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 den 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 Unterbrechnungsanforderung 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 /* Listing1003.java */
002 
003 public class Listing1003
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 	Listing1003 th = new Listing1003();
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 }
Listing1003.java
Listing 10.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 

10.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 Unterbrechnungsstelle fortzusetzen. Beide Methoden sind nicht ganz ungefährlich und können unbemerkt Deadlocks verursachen. Sie wurden daher im JDK 1.2 als deprecated markiert und sollten nicht mehr verwendet werden. Ihre Funktionalität muß - wenn erforderlich - manuell nachgebildet werden.

 JDK1.1/1.2 

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

Die Kapselung des Aufrufs von Thread.sleep innerhalb eines try-catch-Blocks ist erforderlich, weil sleep nach Ablauf der Zeit eine Ausnahme vom Typ InterruptedException erzeugt. Ohne den try-catch-Block würde diese an den Aufrufer weitergegeben werden. 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.

 Hinweis 

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.


 Tit   Inh   Ind   1   2   3   4   5   6   7   8   9   10   11   12   13   14   15   16   17   18   19   20   21   22   23   24   25   26   27   28   29   30   31   32   <<   <   >   >> 
Go To Java 2, Addison Wesley, Version 1.0.2, © 1999 Guido Krüger, http://www.gkrueger.com