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

## 22.3 Das Interface Runnable

- 22.3 Das Interface Runnable
  - 22.3.1 Implementieren von Runnable
  - 22.3.2 Multithreading durch Wrapper-Klassen

Nicht immer ist es möglich, eine Klasse, die als Thread laufen soll, von `Thread` abzuleiten. Dies ist insbesondere dann nicht möglich, wenn die Klasse Bestandteil einer Vererbungshierarchie ist, die eigentlich nichts mit Multithreading zu tun hat. Da Java keine Mehrfachvererbung kennt, kann eine bereits abgeleitete Klasse nicht von einer weiteren Klasse erben. Da sehr unterschiedliche Klassen als Thread parallel zu vorhandenem Code ausgeführt werden können, ist dies eine sehr unschöne Einschränkung des Multithreading-Konzepts von Java.

Glücklicherweise gibt es einen Ausweg. Er besteht darin, einen Thread nicht durch Ableiten aus `Thread`, sondern durch Implementierung des Interfaces `Runnable` zu erzeugen. `Runnable` enthält nur eine einzige Deklaration, nämlich die der Methode `run`:

    public abstract void run()
| java.lang.Runnable

### 22.3.1 Implementieren von Runnable

Tatsächlich muß jede Klasse, deren Instanzen als Thread laufen sollen, das Interface `Runnable` implementieren (sogar die Klasse `Thread` selbst). Um eine nicht von `Thread` abgeleitete Instanz in dieser Weise als Thread laufen zu lassen, ist in folgenden Schritten vorzugehen:

- Zunächst wird ein neues Thread-Objekt erzeugt.
- An den Konstruktor wird das Objekt übergeben, das parallel ausgeführt werden soll.
- Die Methode `start` des neuen Thread-Objekts wird aufgerufen.

Nun startet das `Thread`-Objekt die `run`-Methode des übergebenen Objekts, das sie ja durch die Übergabe im Konstruktor kennt. Da dieses Objekt das Interface `Runnable` implementiert, ist garantiert, daß eine geeignete Methode `run` zur Verfügung steht.

Wir wollen dies an einem Beispiel deutlich machen:

    001 /* Listing2204.java */
    002
    003 class A2204
    004 {
    005   int irgendwas;
    006   //...
    007 }
    008
    009 class B2204
    010 extends A2204
    011 implements Runnable
    012 {
    013    public void run()
    014    {
    015       int i = 0;
    016       while (true) {
    017          if (Thread.interrupted()) {
    018             break;
    019          }
    020          System.out.println(i++);
    021       }
    022    }
    023 }
    024
    025 public class Listing2204
    026 {
    027    public static void main(String[] args)
    028    {
    029       B2204 b = new B2204();
    030       Thread t = new Thread(b);
    031       t.start();
    032       try {
    033          Thread.sleep(1000);
    034       } catch (InterruptedException e){
    035          //nichts
    036       }
    037       t.interrupt();
    038    }
    039 }
| Listing2204.java

Listing 22.4: Implementieren von Runnable

Die Klasse `B2204` ist von `A2204` abgeleitet und kann daher nicht von `Thread` abgeleitet sein. Statt dessen implementiert sie das Interface `Runnable`. Um nun ein Objekt der Klasse `B2204` als Thread auszuführen, wird in `main` von `Listing2204` eine Instanz dieser Klasse erzeugt und an den Konstruktor der Klasse `Thread` übergeben. Nach dem Aufruf von `start` wird die `run`-Methode von `B2204` aufgerufen.

### 22.3.2 Multithreading durch Wrapper-Klassen

Auf eine ähnliche Weise lassen sich auch Methoden, die ursprünglich nicht als Thread vorgesehen waren, in einen solchen umwandeln und im Hintergrund ausführen. Der Grundstein für die Umwandlung eines gewöhnlichen Objekts in einen Thread wird dabei immer bei der Übergabe eines `Runnable`-Objekts an den Konstruktor des `Thread`-Objekts gelegt. Das folgende Beispiel demonstriert, wie eine zeitintensive Primfaktorzerlegung im Hintergrund laufen kann.

Zunächst benötigen wir dazu eine Klasse `PrimeNumberTools`, die Routinen zur Berechnung von Primzahlen und zur Primfaktorzerlegung zur Verfügung stellt. Diese Klasse ist weder von `Thread` abgeleitet, noch implementiert sie `Runnable`:

    001 /* PrimeNumberTools.java */
    002
    003 public class PrimeNumberTools
    004 {
    005   public void printPrimeFactors(int num)
    006   {
    007     int whichprime = 1;
    008     int prime;
    009     String prefix;
    010
    011     prefix = "primeFactors("+num+")= ";
    012     while (num > 1) {
    013       prime = getPrime(whichprime);
    014       if (num % prime == 0) {
    015         System.out.print(prefix+prime);
    016         prefix = " ";
    017         num /= prime;
    018       } else {
    019         ++whichprime;
    020       }
    021     }
    022     System.out.println();
    023   }
    024
    025   public int getPrime(int cnt)
    026   {
    027     int i = 1;
    028     int ret = 2;
    029
    030     while (i < cnt) {
    031       ++ret;
    032       if (isPrime(ret)) {
    033         ++i;
    034       }
    035     }
    036     return ret;
    037   }
    038
    039   private boolean isPrime(int num)
    040   {
    041     for (int i = 2; i < num; ++i) {
    042       if (num % i == 0) {
    043         return false;
    044       }
    045     }
    046     return true;
    047   }
    048 }
| PrimeNumberTools.java

Listing 22.5: Eine Klasse zur Primfaktorzerlegung

Ohne Hintergrundverarbeitung könnte `PrimeNumberTools` instanziert und ihre Methoden durch einfachen Aufruf verwendet werden:

    001 /* Listing2206.java */
    002
    003 import java.io.*;
    004
    005 public class Listing2206
    006 {
    007   public static void main(String[] args)
    008   {
    009     PrimeNumberTools pt = new PrimeNumberTools();
    010     BufferedReader   in = new BufferedReader(
    011                           new InputStreamReader(
    012                           new DataInputStream(System.in)));
    013     int num;
    014
    015     try {
    016       while (true) {
    017         System.out.print("Bitte eine Zahl eingeben: ");
    018         System.out.flush();
    019         num = (new Integer(in.readLine())).intValue();
    020         if (num == -1) {
    021           break;
    022         }
    023         pt.printPrimeFactors(num);
    024       }
    025     } catch (IOException e) {
    026       //nichts
    027     }
    028   }
    029 }
| Listing2206.java

Listing 22.6: Verwendung der Klasse zur Primfaktorzerlegung

Das Programm erzeugt eine Instanz der Klasse `PrimeNumberTools` und führt für jeden eingelesenen Zahlenwert durch Aufruf der Methode `printPrimeFactors` die Primfaktorzerlegung durch. Um nun diese Berechnungen asynchron durchzuführen, entwerfen wir eine Wrapper-Klasse, die von `PrimeNumberTools` abgeleitet wird und das Interface `Runnable` implementiert:

    001 /* ThreadedPrimeNumberTools.java */
    002
    003 public class ThreadedPrimeNumberTools
    004 extends PrimeNumberTools
    005 implements Runnable
    006 {
    007   private int arg;
    008   private int func;
    009
    010   public void printPrimeFactors(int num)
    011   {
    012     execAsynchron(1,num);
    013   }
    014
    015   public void printPrime(int cnt)
    016   {
    017     execAsynchron(2,cnt);
    018   }
    019
    020   public void run()
    021   {
    022     if (func == 1) {
    023       super.printPrimeFactors(arg);
    024     } else if (func == 2) {
    025       int result = super.getPrime(arg);
    026       System.out.println("prime number #"+arg+" is: "+result);
    027     }
    028   }
    029
    030   private void execAsynchron(int func, int arg)
    031   {
    032     Thread t = new Thread(this);
    033     this.func = func;
    034     this.arg  = arg;
    035     t.start();
    036   }
    037 }
| ThreadedPrimeNumberTools.java

Listing 22.7: Primfaktorzerlegung mit Threads

Hier wurde die Methode `printPrimeFactors` überlagert, um den Aufruf der Superklasse asynchron ausführen zu können. Dazu wird in `execAsynchron` ein neuer Thread generiert, dem im Konstruktor das aktuelle Objekt übergeben wird. Durch Aufruf der Methode `start` wird der Thread gestartet und die `run`-Methode des aktuellen Objekts aufgerufen. Diese führt die gewünschten Aufrufe der Superklasse aus und schreibt die Ergebnisse auf den Bildschirm. So ist es möglich, bereits während der Berechnung der Primfaktoren einer Zahl eine neue Eingabe zu erledigen und eine neue Primfaktorberechnung zu beginnen.

Um dies zu erreichen, ist in der Klasse `Listing2206` lediglich die Deklaration des Objekts vom Typ `PrimeNumberTools` durch eine Deklaration vom Typ der daraus abgeleiteten Klasse `ThreadedPrimeNumberTools` zu ersetzen:

    001 /* Listing2208.java */
    002
    003 import java.io.*;
    004
    005 public class Listing2208
    006 {
    007   public static void main(String[] args)
    008   {
    009     ThreadedPrimeNumberTools pt;
    010     BufferedReader in = new BufferedReader(
    011                         new InputStreamReader(
    012                         new DataInputStream(System.in)));
    013     int num;
    014
    015     try {
    016       while (true) {
    017         System.out.print("Bitte eine Zahl eingeben: ");
    018         System.out.flush();
    019         num = (new Integer(in.readLine())).intValue();
    020         if (num == -1) {
    021            break;
    022         }
    023         pt = new ThreadedPrimeNumberTools();
    024         pt.printPrimeFactors(num);
    025       }
    026     } catch (IOException e) {
    027       //nichts
    028     }
    029   }
    030 }
| Listing2208.java

Listing 22.8: Verwendung der Klasse zur Primfaktorzerlegung mit Threads

Wenn alle Eingaben erfolgen, bevor das erste Ergebnis ausgegeben wird, könnte eine Beispielsitzung etwa so aussehen (Benutzereingaben sind fett gedruckt):

    Bitte eine Zahl eingeben: 991
    Bitte eine Zahl eingeben: 577
    Bitte eine Zahl eingeben: 677
    Bitte eine Zahl eingeben: -1
    primeFactors(577)= 577
    primeFactors(677)= 677
    primeFactors(991)= 991

|  | Obwohl das gewünschte Verhalten (nämlich die asynchrone Ausführung einer zeitaufwendigen Berechnung im Hintergrund) realisiert wird, ist dieses Beispiel nicht beliebig zu verallgemeinern. Die Ausgabe erfolgt beispielsweise nur dann ohne Unterbrechung durch Benutzereingaben, wenn alle Eingaben vor der ersten Ausgabe abgeschlossen sind. Selbst in diesem Fall funktioniert das Programm nicht immer zuverlässig. Es ist generell problematisch, Hintergrundprozessen zu erlauben, auf die Standardein- oder -ausgabe zuzugreifen, die ja vorwiegend vom Vordergrund-Thread verwendet wird. Ein- und Ausgaben könnten durcheinander geraten und es könnte zu Synchronisationsproblemen kommen, die die Ausgabe verfälschen. Wir haben nur ausnahmsweise davon Gebrauch gemacht, um das Prinzip der Hintergrundverarbeitung an einem einfachen Beispiel darzustellen. |  |
Hinweis

Das nächste Problem ist die Realisierung des Dispatchers in `run`, der mit Hilfe der Instanzvariablen `func` und `arg` die erforderlichen Funktionsaufrufe durchführt. Dies funktioniert hier recht problemlos, weil alle Methoden dieselbe Parametrisierung haben. Im allgemeinen wäre hier ein aufwendigerer Übergabemechanismus erforderlich.

Des weiteren sind meistens Vorder- und Hintergrundverarbeitung zu synchronisieren, weil der Vordergrundprozeß die Ergebnisse des Hintergrundprozesses benötigt. Auch hier haben wir stark vereinfacht, indem die Ergebnisse einfach direkt nach der Verfügbarkeit vom Hintergrundprozeß auf den Bildschirm geschrieben wurden. Das Beispiel zeigt jedoch, wie prinzipiell vorgegangen werden könnte, und ist vorwiegend als Anregung für eigene Experimente anzusehen.

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