Titel   Inhalt   Suchen   Index   DOC  Handbuch der Java-Programmierung, 7. Auflage
 <<    <     >    >>   API  Kapitel 23 - Multithreading

23.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 Interface Runnable zu erzeugen. Runnable enthält nur eine einzige Deklaration, nämlich die der Methode run:

public abstract void run()
java.lang.Runnable

23.3.1 Implementieren von Runnable

Tatsächlich muss 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, dass eine geeignete Methode run zur Verfügung steht.

Wir wollen dies an einem Beispiel deutlich machen:

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

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

23.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 23.5: Eine Klasse zur Primfaktorzerlegung

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

001 /* Listing2306.java */
002 
003 import java.io.*;
004 
005 public class Listing2306
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 }
Listing2306.java
Listing 23.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 23.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 Listing2306 lediglich die Deklaration des Objekts vom Typ PrimeNumberTools durch eine Deklaration vom Typ der daraus abgeleiteten Klasse ThreadedPrimeNumberTools zu ersetzen:

001 /* Listing2308.java */
002 
003 import java.io.*;
004 
005 public class Listing2308
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 }
Listing2308.java
Listing 23.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 zeitaufwändigen 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 durcheinandergeraten 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 aufwändigerer Übergabemechanismus erforderlich.

Des Weiteren sind meistens Vorder- und Hintergrundverarbeitung zu synchronisieren, weil der Vordergrundprozess die Ergebnisse des Hintergrundprozesses benötigt. Auch hier haben wir stark vereinfacht, indem die Ergebnisse einfach direkt nach der Verfügbarkeit vom Hintergrundprozess 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, 7. Auflage, Addison Wesley, Version 7.0
 <<    <     >    >>   API  © 1998, 2011 Guido Krüger & Heiko Hansen, http://www.javabuch.de