Advanced   Java   Services Runnable, Thread, Callable, ExecutorService Back Next Up Home


Grundlagen

Threads, auch nebenläufige Prozesse genannt, ermöglichen vereinfacht gesagt, Multitasking innerhalb eines Programms. Bestimmte Programmabschnitte kann der Programmierer parallel ablaufen lasssen. Java stellt Mechanismen zur Verzweigung bereit, so daß bestimmte Programmabschnitte parallel (Mehrprozessorsysteme) oder quasiparallel (Einprozessorsysteme) ablaufen können. Für diese Fälle müssen Mechanismen zur Prozeßkommunikation bereitgestellt werden. Greifen mehrere Prozesse gleichzeitig auf ein Objekt zu muß man Mechanismen zur Hand haben, um dies gegebenenfalls zu unterbinden um Datenkonsistenz zu gewährleisten. Hierzu gibt es verschiedene Möglichkeiten zur Synchronisation von Prozessen.


Threads erzeugen und aufrufen

Threads können vom Programmierer nur eingeschränkt konfiguriert werden, da letzten Endes das Betriebssystem ihren Ablauf bestimmt. Das Betriebssystem ordnet Sie in eine Warteschlange ein, wo sie dann auf Zuteilung von Prozessorzeit warten. Java stellt die Methoden public void run() und public <T> T call() zur Verfügung um Code zu schreiben, der in einem eigenen Thread laufen soll. In diese Methoden werden die Aufgaben aus main() oder auch anderen Methoden ausgelagert, die parallel laufen sollen. Diese Methoden dürfen aber nicht vom Programm selbst aufgerufen werden, sondern müssen über einen eigenen Startmechanismus gerufen werden, damit sie wirklich parallel zum aufrufenden Elternprozess ablaufen. Auch müssen diese Methoden besonders gekennzeichnet werden, damit sie als Methoden für Threads erkannt werden. Dazu dienen Interfaces.


Das Interface Runnable

Das Interface Runnable vereinbart die Methode run().

java.lang.Runnable
ReturntypName der Methode
voidrun()
When an object implementing interface Runnable is used to create a thread, starting the thread causes the object's run method to be called in that separately executing thread.


Die Klasse Thread

Die Klasse Thread implementiert das Interface Runnable und stellt die Methode start() bereitet. start() teilt dem Betriebssystem mit Hilfe der JVM mit, daß mit der Methode run() ein eigener Prozeß gestartet werden soll und ruft dann run() auf. Nur über diesen Umweg läuft run() in einem eigenem Prozeß ab.

java.lang.Thread
ReturntypName der Methode
voidstart()
Causes this thread to begin execution; the Java Virtual Machine calls the run method of this thread.


Threadzustände

Es gibt verschiedene Wartezustände für einen Thread. Ein Thread kann auf einen anderen (oder auf mehrere) warten (join), der Thread kann sich selbst in einen Wartezustand setzen (sleep), er kann auf auf ein Signal warten (wait), er kann sich aus der Prozeßwarteschlange zurückziehen und sich gleich wieder hintern anstellen (yield) und schließlich kann es sein, daß er blockiert ist, weil er auf eine bestimmte Resource nicht zugreifen kann, weil diese gerade ein anderer Prozeß verwendet und die Resource exklusiv vergeben wird.

ThreadZustaende3.jpg

Die folgende Tabelle listet die Konstanten der Enumklasse Thread.State auf. Thread.State ist eine statische innere Klasse von Thread. Die Konstanten werden wie folgt angesprochen

Thread.State state = Thread.State.RUNNABLE;
ZustandBeschreibung
BLOCKEDThread state for a thread blocked waiting for a monitor lock.
NEWThread state for a thread which has not yet started.
RUNNABLEThread state for a runnable thread.
TERMINATEDThread state for a terminated thread.
TIMED_WAITINGThread state for a waiting thread with a specified waiting time.
WAITINGThread state for a waiting thread.


Der Begriff lock wird im Kapitel Synchronisieren mit synchronized erklärt.


Ausgabe einiger Threadzustände

Das folgende Codeschnipsel zeigt die Verwendung.

private static void threadStateDemo()
{
  Thread.State state = Thread.State.RUNNABLE;

  Thread thread = new Thread();
  System.out.println(thread.getState());
  thread.start();
  System.out.println(thread.getState());

  try
  {
    thread.join();
  }
  catch(InterruptedException ex)
  {
    ex.printStackTrace();
  }
  System.out.println(thread.getState());
}

Der Ablauf ergibt folgende Ausgabe

NEW
RUNNABLE
TERMINATED

Erzeugen von Threads

Es gibt grundsätzlich nur wenige Varianten mit run() Threads zu erzeugen.


Eine Unterklasse von Thread bilden
public class MyThread extends Thread
{
   public MyThread()
   {
   }

   @Override
   public void run()
   {
      // TODO Auto-generated method stub
   }
}

Einsetzen eines Objektes dieser Klasse in einem anderen Thread, etwa dem Mainthread.

public class Main
{
   public static void main(String[] args)
   {
      //...
      MyThread myThread = new MyThread();
      myThread.start();
      //...
   }
}

Das Interface Runnable implementieren und einem Service übergeben

Da Java keine Mehrfachvererbung kennt, kann die obige Methode nicht immer verwendet werden. Als Alternative implementiert eine beliebige Klasse das Interface Runnable. Jetzt braucht man noch ein Objekt, dem man ein Runnableobjekt übergeben kann und das die zugehörige run-Methode als eigenen Thread startet.


Die Klasse Thread als Service verwenden

Das einfachste ist, die Klasse Thread als Startservice zu verwenden. Thread bietet dafür einen Konstruktor Thread(Runnable target) an, dem man ein Runnableobjekt übergeben kann. Das so erzeugte Threadobjekt verwendet beim Starten dann nicht das eigene run sondern target.run(). Auf diese Weise wird die Beschränkung der fehlenden Mehrfachvererbung umgangen.

public class MyRunnable implements Runnable
{
   public MyRunnable()
   {
   }

   @Override
   public void run()
   {
      // TODO Auto-generated method stub
   }
}
public class Main
{
   public static void main(String[] args)
   {
      //...
      MyRunnable myRunnable = new MyRunnable();
      Thread thread = new Thread(myRunnable);
      myThread.start();
      //...
   }
}

Einen ExecutorService als Service verwenden

Ab der Version 1.5 steht Javaentwicklern das concurrent-Package zur Verfügung, das maßgeblich von Doug Lea entworfen und auch geschrieben worden ist. Das Paket bietet ein breites Spektrum von Klassen an, die das Arbeiten mit Threads erleichtern und sicherer machen. Dazu führt Das concurrent-Package u.a. sog. ExecutorServices an. Mit Hilfe der Factoryklasse Executors kann man sich verschiedene vorkonfigurierte Executoren erzeugen. Wir erzeugen hierzu einen Service, der genau einen Thread starten kann, einen SingleThreadExecutor und verwenden ihn um den Thread zu starten.

public class MyRunnable implements Runnable
{
   public MyRunnable()
   {
   }

   @Override
   public void run()
   {
      // TODO Auto-generated method stub
   }
}
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;

public class Main
{
   public static void main(String[] args)
   {
      //...
      MyRunnable myRunnable = new MyRunnable();
      ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
      singleThreadExecutor.execute(myRunnable);
      singleThreadExecutor.shutDown();
      //...
   }
}

Man beachte, daß bei dieser Methode kein Threadobjekt erzeugt wird. Man kann also keine der Wartemethoden aufrufen, die oben erwähnt wurden. Will man das, so muß man die Klasse MyRunnable entsprechend ausbauen oder man übergibt dem ExecutorService ein Threadobjekt, überläßt aber das Starten dem Executorservice. Außerdem ist zu beachten, daß ein ExecutorService von Hand zu beenden ist. Ohne shutDown() wartet der ExecutorService auf weitere Runnables.


Das Interface Callable

Im concurrent-Package gibt es das Interface Callable. Es stellt die Methode call() bereit, die mehr Möglichkeiten bietet als run() da sie einen Returnwert hat und eine Exception werfen kann. Der Returntyp der Methode call() wird über den generischen Typ des Interfaces Callable festgelegt.

java.util.concurrent.Callable<V>
ReturntypName der Methode
Vcall()
Computes a result, or throws an exception if unable to do so.


Erzeugen eines Threads mit einem Callable

Wie bei Runnable braucht man eine Klasse, die das Interface Callable implementiert. Bei der Implementierung wird der generische Typ des Interfaces zum Returntyp der Methode call(). Wir nehmen String als Returntyp.

public class MyCallable implements Callable<String>
{
   public MyCallable()
   {
   }

   @Override
   public String call()
   {
      // TODO Auto-generated method stub
      return "result";
   }
}

Statt eines POJO (plain old java object) kann man natürlich auch eine anonyme Klasse oder einen Lambdaausdruck (siehe Java 8) verwenden.


Einen ExecutorService für ein Callable erstellen

Ein Callable kann man mit Hilfe eines Executorservice starten. Um den Returnwert von call() zu bekommen muß man etwas mehr Aufwand betreiben. Ein ExecutorService stellt eine Methode submit(Callable<T> task) bereit. Mit dieser Methode kann man ein Callable starten. submit() gibt ein Objekt vom Typ Future<T> zurück. Dieser Typ besitzt u.a. eine Methode get() und genau diese Methode liefert uns den Returnwert von call(). Nch dem Absetzen von submit() kann man das Callable nicht mehr beeinflußen.

import java.util.concurrent.Future;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ExecutionException;

public class Main
{
   public static void main(String[] args)
   {
      //...
      ExecutorService es = Executors.newSingleThreadExecutor();
      Future<String> future = es.submit(new MyCallable());
      try
      {
         System.out.println(future.get());
         // get blockiert, bis das Ergebnis zur Verfügung steht!
      }
      catch(InterruptedException | ExecutionException ex)
      {
         // TODO Auto-generated catch block
         ex.printStackTrace();
      }
      es.shutDown();
      //...
   }
}

Anders als ein über start() erzeugter Thread beendet sich ein ExecutorService nicht automatisch, sondern muß mit der Methode shutDown() explizit beendet werden.





Valid XHTML 1.0 Strict top Back Next Up Home