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.3 Das Interface Runnable



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

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

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 /* Listing1004.java */
002 
003 class A1004
004 {
005    int irgendwas;
006    //...
007 }
008 
009 class B1004
010 extends A1004
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 Listing1004
026 {
027    public static void main(String args[])
028    {
029       B1004 b = new B1004();
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 }
Listing1004.java
Listing 10.4: Implementieren von Runnable

 Beispiel 

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

10.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 
049 }
PrimeNumberTools.java
Listing 10.5: Eine Klasse zur Primfaktorzerlegung

 Beispiel 

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

001 /* Listing1006.java */
002 
003 import java.io.*;
004 
005 public class Listing1006
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 }
Listing1006.java
Listing 10.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. Daß hier einige I/O-Routinen von Java verwendet wurden, braucht Sie nicht zu beunruhigen; wir kommen in Kapitel 13 auf sie zurück.

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 10.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 Listing1006 lediglich die Deklaration des Objekts vom Typ PrimeNumberTools durch eine Deklaration vom Typ der daraus abgeleiteten Klasse ThreadedPrimeNumberTools zu ersetzen:

001 /* Listing1008.java */
002 
003 import java.io.*;
004 
005 public class Listing1008
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 }
Listing1008.java
Listing 10.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.


 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