Guido Krüger - JAVA 1.1 lernen - Kapitel 10

Previous Page Previous Page TOC TOC Index Next Page Previous Page



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 interface runnable
{
   public abstract void run();
}

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

Beispiel

Wir wollen dies an einem Beispiel deutlich machen:

class A_1004
{
   int irgendwas;
   //...
}

class B_1004
extends A_1004
implements Runnable
{
   public void run()
   {
      int i = 0;
      while (true) {
         System.out.println(i++);
      }
   }
}

public class Example1004
{
   public static void main(String args[])
   {
      B_1004 b = new B_1004();
      Thread t = new Thread(b);
      t.start();
      try {
         Thread.sleep(1000);
      } catch (InterruptedException e){
         //nichts
      }
      t.stop();
   }
}

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

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.

Beispiel

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:

class PrimeNumberTools
{
   public void printPrimeFactors(int num)
   {
      int whichprime = 1;
      int prime;
      String prefix;

      prefix = "primeFactors("+num+")= ";
      while (num > 1) {
         prime = getPrime(whichprime);
         if (num % prime == 0) {
            System.out.print(prefix+prime);
            prefix = " ";
            num /= prime;
         } else {
            ++whichprime;
         }
      }
      System.out.println();
   }

   public int getPrime(int cnt)
   {
      int i = 1;
      int ret = 2;

      while (i < cnt) {
         ++ret;
         if (isPrime(ret)) {
            ++i;
         }
      }
      return ret;
   }

   private boolean isPrime(int num)
   {
      for (int i = 2; i < num; ++i) {
         if (num % i == 0) {
            return false;
         }
      }
      return true;
   }

}

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

import java.io.*;

public class Example1005 {
   public static void main(String[] args)
   {
      PrimeNumberTools pt = new PrimeNumberTools();
      BufferedReader   in = new BufferedReader(
                            new InputStreamReader(
                            new DataInputStream(System.in)));
      int num;

      try {
         while (true) {
            System.out.print("Bitte eine Zahl eingeben: ");
            System.out.flush();
            num = (new Integer(in.readLine())).intValue();
            if (num == -1) {
               break;
            }
            pt.printPrimeFactors(num);
         }
      } catch (IOException e) {
         //nichts
      }
   }
}

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 später 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:

class ThreadedPrimeNumberTools
extends PrimeNumberTools
implements Runnable
{
   private int arg;
   private int func;

   public void printPrimeFactors(int num)
   {
      execAsynchron(1,num);
   }

   public void printPrime(int cnt)
   {
      execAsynchron(2,cnt);
   }

   public void run()
   {
      if (func == 1) {
         super.printPrimeFactors(arg);
      } else if (func == 2) {
         int result = super.getPrime(arg);
         System.out.println("prime number #"+arg+" is: "+result);
      }
   }

   private void execAsynchron(int func, int arg)
   {
      Thread t = new Thread(this);
      this.func = func;
      this.arg  = arg;
      t.start();
   }
}

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

import java.io.*;

public class Example1006 {
   public static void main(String[] args)
   {
      ThreadedPrimeNumberTools pt;
      BufferedReader in = new BufferedReader(
                          new InputStreamReader(
                          new DataInputStream(System.in)));
      int num;

      try {
         while (true) {
            System.out.print("Bitte eine Zahl eingeben: ");
            System.out.flush();
            num = (new Integer(in.readLine())).intValue();
            if (num == -1) {
               break;
            }
            pt = new ThreadedPrimeNumberTools();
            pt.printPrimeFactors(num);
         }
      } catch (IOException e) {
         //nichts
      }
   }
}

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

Hinweis

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.

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.


Previous Page Previous Page Page Top TOC Index Next Page Previous Page

(C) 1997 Guido Krueger, "Java 1.1 lernen", Addison-Wesley, Bonn, 1997