InfoHome | Themen | Projekte | Links | Software |
|
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.
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 {
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 {
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 {
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 {
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 {
Es ändert sich ansonsten nichts ...
public class Main3 {
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 ThreadIn der Klasse Thread finden sich neben start() noch weitere nützliche Methoden zum Umgang mit Threads. Hier die wichtigsten im Überblick:
Der Lebenszyklus eines ThreadsVon 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. |
© 2004-2024 M. Blanke · Ursulaschule · Kleine Domsfreiheit 11-18 · 49074 Osnabrück |