Logo Logo
InfoHome Themen Projekte Links Software
Themen
JavaHamster
BlueJ
Java
Sprachelemente
Abstrakte Datentypen
Swing
Composite Pattern
AWT > Swing
GUI-Grundstruktur
Farben und Fonts
Layout-Manager
Komponenten 1
Komponenten 2
Komponenten 3
Container
Observer Pattern
Ereignisverarbeitung
MVC-Pattern
Game Of Life
Threads
Aufgaben
Sortieren
HTML
XHTML
CSS
XML
Datenbanken
MySQL
Theoretische Informatik
PHP
Kara
Lego-Roboter
Algorithmen

Threads

Mit Hilfe von Threads (deutsch: "Faden") lässt sich in Java das Konzept der nebenläufigen, parallelen Programmierung umsetzen. Ein Thread ist sozusagen ein zusätzlicher Programmfluss, der parallel zum eigentlichen Programmfluss abgearbeitet wird.
Echte parallele (also gleichzeitige) Ausführung von Programmteilen ließe sich allerdings nur auf einem Rechner realisieren, der mit mehreren Prozessoren ausgestattet ist. Wenn dieses nicht der Fall ist, kann sich Parallelität nur auf eine Quasi-Gleichzeitigkeit beschränken. Der Prozessor wechselt zur Laufzeit andauernd zwischen den verschiedenen aktiven Threads, so das für den Benutzer der Eindruck der zeitgleichen Ausführung entsteht.

Ohne dass es uns bewusst war, haben wir in Java von Anfang mit Threads zu tun gehabt. Der sogenannte main-Thread ist für die Ausführung der main-Methode zuständig. Darüberhinaus kümmert sich bei sämtlichen Programmen mit grafischer Oberfläche ein weiterer automatisch erzeugter Thread um die Ereignisverarbeitung im Zusammenhang mit den verschiedenen Komponenten der Oberfläche.

In Java ist es sehr einfach möglich, eigene Threads zu erzeugen. Darum soll es in diesem Abschnitt gehen. Ziel ist es insbesondere unsere GameOfLife-Simulation in einem Thread automatisch ablaufen zu lassen.

ABCPrinter (1)

In dieser einfachen Beispielapplikation sagen zwei ABCPrinter-Objekte auf der Kommandozeile das ABC auf. Die erste Iteration dieses Projektes benutzt noch keine Threads.

public class ABCPrinter {

  public void run() {
    for(char b = 'A'; b = 'Z'; b++){
      System.out.print(b);

      try {
        Thread.sleep(1000);
      }
      catch (InterruptedException e){

      }
    }
  }

  public void start(){
    run();
  }
}

Ein ABCPrinter-Objekt verfügt über die Methoden run() und start(). Die Methode run() enthält die Aktionen, die später innerhalb eines eigenen Threads ablaufen sollen (hier die Ausgabe des Alphabets von A bis Z). Innerhalb des try-catch-Blockes ist eine Zeitverzögerung um 1000ms = 1s zwischen der Ausgabe von zwei Buchstaben eingebaut. Das try-catch-Konstrukt dient allgemein zur Fehlerbehandlung in Java. Fehler werden in Form von der JVM in Form von Exception-Objekten gemeldet. Das try-catch-Konstrukt besagt: Versuche (try) die Anweisungen im try-Block auszuführen. Tritt dabei eine bestimmte Fehlermeldung auf, lässt sich innarhalb des catch-Blockes eine geeignete Reaktion programmieren. Der catch-Block ist fester Bestandteil des Konstrukts. Die Methode sleep() der Klasse Thread lässt sich nur in dieser Form abgesichert aufrufen (Erklärung evtl. weiter unten...) Die Zeile

Thread.sleep(1000);

sorgt dafür, dass der aktuelle Thread pausiert. In der ersten Iteration des Projektes ist das der main-Thread, da wie gesagt noch keine eigenen Threads erzeugt werden. Durch Aufruf der Methode start() beginnen die ABCPrinter mit der Ausgabe von Buchstaben.

public class Main1 {

  public static void main(String[] args) {

    ABCPrinter p1 = new ABCPrinter();
    ABCPrinter p2 = new ABCPrinter();

    p1.start();
    p2.start();

  }

}

Das Hauptprogramm versteht sich von selbst. Die Konsolen-Ausgabe lautet:

ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ

Wie erwartet sagen unsere beiden ABCPrinter-Objekte das Alphabet hier nacheinander auf und eben noch nicht parallel.

ABCPrinter (2)

In den nächsten beiden Iterationen des Projektes sollen die beiden Möglichkeiten, mit denen man in Java eigene Threads erzeugen kann, vorgestellt werden. In der ersten Variante leitet man die Klasse, in der die Aktionen die nebenläufig ausgeführt werden sollen (also im gerade das, was oben in der Methode run() implementiert worden ist), einfach von der Klasse Thread ab.

public class ABCPrinterThread extends Thread {

  public void run() {
    for(char b = 'A'; b = 'Z'; b++){
      System.out.print(b);

      try {
        Thread.sleep(1000);
      }
      catch (InterruptedException e){

      }
    }
  }

}

Einzige Änderung neben dem Ableiten ist das Fehlen der Methode start(). Diese Methode, die weiterhin zum Starten des Threads verwendet wird, erbt man von der Klasse Thread. Der Aufruf von start() an einem Thread-Objekt ist kein "normaler" Methodenaufruf. start() setzt den Prozess der nebenläufigen Ausführung eines Programmteils in Gang. Ein expliziter Aufruf der Methode run() starten dagegen nicht die nebenläufige Abarbeitung, sondern wird wie ein einfacher Methodenaufruf behandelt.

public class Main2 {

  public static void main(String[] args) {

    ABCPrinterThread t1 = new ABCPrinterThread();
    ABCPrinterThread t2 = new ABCPrinterThread();

    t1.start();
    t2.start();

  }

}

Im Hauptprogramm ändert sich gar nichts und dennoch lautet die Ausgabe jetzt

AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ

Die ABCPrinter-Objekte arbeiten nun parallel, jeder in seinem eigenen Thread.

ABCPrinter (3)

In Java gibt es keine Mehrfachvererbung. Aus diesem Grund ist die 2. Iteration unseres ABCPrinters in manchen Fällen unpraktikabel. Dies ist der Fall, wenn die Klasse, die die run()-Methode enthält bereits von einer anderen Klasse abgeleitet ist. Aus diesem Grund gibt es noch eine zweite Möglichkeit, einen eigenen Thread mit Hilfe des Interfaces Runnable zu erzeugen. Eine Klasse die das Interface Runnable implementiert, muss eine Methode run() enthalten. Ein Objekt dieser Klasse erfüllt dann die Voraussetzung dafür, Aktionen in einem Thread nebenläufig auszuführen.

public class ABCPrinterRunnable implements Runnable {

  public void run() {
    for(char b = 'A'; b = 'Z'; b++){
      System.out.print(b);

      try {
        Thread.sleep(1000);
      }
      catch (InterruptedException e){

      }
    }
  }

}

Es ändert sich ansonsten nichts ...

public class Main3 {

  public static void main(String[] args) {

    Runnable r1 = new ABCPrinterRunnable();
    Runnable r2 = new ABCPrinterRunnable();

    Thread t1 = new Thread(r1);
    Thread t2 = new Thread(r2);

    t1.start();
    t2.start();

  }

}

Im Hauptprogramm werden zunächst zwei Runnable-Objekte (unsere ABCPrinter) erzeugt. Man beachte, dass hier der Interface-Name als Typname benutzt werden kann und muss (ein weiteres Bsp. für "Polymorphie"). Runnable-Objekte können dann einem Thread-Konstruktor übergeben werden. Die Ausgabe ist mit der Ausgabe der 2. Iteration identisch.

Die Klasse Thread

In der Klasse Thread finden sich neben start() noch weitere nützliche Methoden zum Umgang mit Threads. Hier die wichtigsten im Überblick:

Der Lebenszyklus eines Threads

Von seiner Erzeugung bis zum Ende seiner Ausführung durchläuft eine Thread im Rahmen seines Lebenszyklus verschiedene Zustände, die in der Abbildung schematisch dargestellt sind:

Wenn ein Thread mit Hilfe des new-Operators erzeugt worden ist, befindet er sich im Zustand erzeugt (new thread). Durch Aufruf der methode start() wird er in den Zustand ausführbar (runnable) versetzt. Da sich der Thread die Prozessorzeit mit anderen Threads teilen muss, wird die Ausführung seiner run-Methode immer wieder unterbrochen, damit auch die übrigen Threads ihre Ausführung fortsetzen können. Die Aufteilung der Prozessorarbeit auf die verschiedenen aktiven Threads übernimmt der Scheduler der virtuellen Maschine. Mit der Klassenmethode yield() kann der gerade ausgeführte Thread auch selbst dafür sorgen, dass er den Prozessor unabhängig von Verteilungsmechanismus des Schedulers freigibt.

Die Klassenmethode sleep() versetzt den aktuellen Thread für die Dauer der angegebenen Zeit in den Zustand nicht ausführbar (not runnable), in dem ihm vom Scheduler keine Prozessorzeit zugeteilt wird. Der Thread wechselt nach Ablauf der Schlafenszeit in den Zustand ausführbar und wird bei der Prozessorzeitaufteilung wieder berücksichtigt. Auch im Rahmen des Zusammenspiels von mehreren Threads ist es notwendig, die automatische Prozessorzeitaufteilung zu beeinflussen. Dazu können Threads mit Hilfe der Methoden wait, notify und notifyAll miteinander kommunizieren und sich selbst in den Zustand nicht ausführbar bzw. andere Threads in den Zustand nicht ausführbar versetzen. Mit Hilfe der Methode isAlive() lässt sich prüfen, ob sich ein Thread im Zustand ausführbar bzw. nicht ausführbar (Rückgabewert: true) oder aber erzeugt bzw. beendet (Rückgabewert: false) befindet.

In den Zustand beendet gelangt ein Thread erst dann, wenn seine run-Methode vollständig abgearbeitet oder aufgrund einer nicht behandelten Ausnahme abgebrochen wurde.

Seit Java 1.5 lässt sich der Zustand des aktuellen Threads mit Hilfe der Klassenmethode getState() ermitteln.

» drucken: pdf | html

© 2004-2024 M. Blanke · Ursulaschule · Kleine Domsfreiheit 11-18 · 49074 Osnabrück