Inhaltsverzeichnis API Einführung Java Entwicklungs-Kit Erste Programme Java-Grundwissen Erste Objekte Ein/Ausgabe in Java Oft benutzte Java-Werkzeuge Graphische Oberfläche Applets Threads Netzwerk Das TopDraw-Projet

Thread-Programmierung

  1. Thread-Programmierung
    1. Mehrere Sachen gleichzeitig machen
      1. Priorität
      2. Erzeugung
        1. von Thread erben
        2. Das Interface Runnable erben
      3. Lebenszyklus eines Threads
        1. Erzeugung eines Threads
        2. Starten eines Threads
        3. Eine Thread in den nicht-laufenden Zustand bringen
        4. Stoppen eines Threads
    2. Übungen
    3. Synchronisation von Threads
    4. timer
    5. Übung
Mit Threads werden selbständige Prozesse, die im inneren von Programmen laufen bezeichnet. Damit können bestimmte Probleme parallelisiert werden, oder allgemein, mehrere Aufgaben können damit gleichzeitig gelöst werden.

Mehrere Sachen gleichzeitig machen

Im Falle eines einzelnen Programms, läuft das Programm sequentiell seine Instruktionen durch, wie im nebenstehenden Bild dargestellt. Wie hier gesehen werden kann, gibt es nur einen geraden Asuführungsfluß. Oft wäre es allerdings sinnvoll, mehrere solcher Flüsse zu haben. Beispielsweise das Einladen einer Datei, kann beträchtliche Zeit rauben, genauso wie das abholen von Ressourcen aus dem Netz. Ein weiteres Beispiel sind Kommandozeilen, bei weöchen es für den Benutzer sehr irritierend ist, wenn diese nur langsam reagiert.
Dies ist dadurch lösbar, daß der langsame Prozeß in einen separaten Ausführungsstrang eingepackt wird, was z.B. folgendermaßen aussehen könnte: Je nach Thread-Modell des Betriebssystems werden nun die Prozesse idealerweise nebeneinander ausgeführt. Wodurch z.B. der langsame Prozeß nicht mehr z.B. die Benutzereingabe verlangsamt.

Priorität

Es wurde gesagt "Je nach Thread-Modell", was heißt dies genau:

In den meisten Fällen wird auf einem mono-Prozessorsystem gearbeitet, selbst wenn es sich um ein mehr-Prozessorsystem handelt, können die Anzahl verlangter Threads die Anzahl installierter Prozessoren übersteigen. In diesem Fall wird das parallele Verarbeiten nur simuliert.

D.h. ein Thread läuft nur dann wenn gerade die Kapazität dazu frei ist, d.h. ein anderer Thread gerade Zyklen-freigibt (mit yield();), anhält oder sich auflöst; auf Betriebsysteme die nach dem sogenannten Time-slicing Verfahren arbeiten, sit eine weitere Möglichkeit, daß ein Thread seine maximale Anzahl Zyklen aufgebraucht hat. Stehen mehrere threads zur bearbeitung aus wird der mit der höchsten Priorität ausgewählt. Muß zwischen gleich-prioritären Threads gewählt werden, werden sie zyklisch ausgewählt.

Es ist möglich (mit setPriority) die Priorität eines Threads zu verändern, sonst behält dieser die die er von seinem Elter bekommen hat.

Erzeugung

Je nach Gebrauch gibt es 2 Möglichkeiten Threads zu erzeugen. Dies kommt daher, daß es in vielen Fällen nicht möglich ist eine Klasse direkt von Thread abzuleiten, da sie von anderen Klassen erbt. Um dennoch dies zu erreichen, wird die Funktionalität Thread als erbare Klasse und als Interface angeboten.

von Thread erben

Gleich das Beispiel aus dem java-Tutrial:
public class SimpleThread extends Thread
{
    public SimpleThread(String str)
    {
      super(str);
    }
    public void run()
    {
       for (int i = 0; i < 10; i++)
       {
          System.out.println(i + " " + getName());
          try { sleep((long)(Math.random() * 1000)); }
          catch (InterruptedException e) {}
       }// for (int i = 0; i < 10; i++)
       System.out.println("DONE! " + getName());
    }// public void run()
}// public class SimpleThread extends Thread

Diese Klasse erbt von Thread, das auffälligste ist, daß die Methode run entsprechend ausgebaut werden muß, dies ist die Methode, welche aufgerufen wird wenn der Thread gestartet wird. Ist die Ausführung in run() zuende, stirbt auch der Thread. In diesem Fall wird der Thread 10 mal aufweckbar sein, danach wird er zu existieren aufgehört haben.

Der Inhalt der run-Methode ist entsprechend: eine Schleife mit 10 Durchläufen, eine Ausgabe, x mal 1s warten und weiterschleifen.

Um diese Klasse zu testen, kann folgendes Programm benutzt werden:

public class TwoThreadsTest
{
    public static void main (String[] args)
    {
        new SimpleThread("Jamaica").start();
        new SimpleThread("Fiji").start();
    }// public static void main (String[] args)
}// public class TwoThreadsTest
Damit bekäme man beispielsweise folgende Ausgabe:
0 Jamaica
0 Fiji
1 Fiji
1 Jamaica
2 Fiji
2 Jamaica
3 Fiji
3 Jamaica
4 Jamaica
4 Fiji
5 Jamaica
5 Fiji
6 Jamaica
6 Fiji
7 Fiji
8 Fiji
9 Fiji
7 Jamaica
8 Jamaica
9 Jamaica
DONE! Fiji
DONE! Jamaica

Das Interface Runnable erben

Manchmal ist es nicht möglich von Thread zu erben, speziell z.B. im Falle von Applets:
import java.awt.Graphics;
import java.util.*;
import java.text.DateFormat;
import java.applet.Applet;

public class Clock extends Applet implements Runnable {
    private Thread clockThread = null;
    public void start() {
        if (clockThread == null) {
            clockThread = new Thread(this, "Clock");
            clockThread.start();
        }
    }
    public void run() {
        Thread myThread = Thread.currentThread();
        while (clockThread == myThread) {
            repaint();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e){
            // the VM doesn't want us to sleep anymore,
            // so get back to work
            }
        }
    }
    public void paint(Graphics g) {
        // get the time and convert it to a date
        Calendar cal = Calendar.getInstance();
        Date date = cal.getTime();
        // format it and display it
        DateFormat dateFormatter = DateFormat.getTimeInstance();
        g.drawString(dateFormatter.format(date), 5, 10);
    }
    // overrides Applet's stop method, not Thread's
    public void stop() {
        clockThread = null;
    }
}

Hier wird die Methode run implementiert, und die Konformität dieser Klasse zum Typ Runnable erzwungen indem das Interface Runnable implementiert wurde.

Damit ist es möglich dieses Objekt einem Thread zu übergeben, dessen einzige Aktivität das Ausführen der Run-Methode unseres Objektes ist.

Lebenszyklus eines Threads

Wir haben jetzt zwei Anwendungsbeispiele für Threads gesehen, ein paar Details zu ihrem Lebenszyklus sind noch wichtig. Es ist jederzeit möglich den Zustand eines Thread über seine isAlive()-Methode abzufragen, kommt false zurück ist der Thread entweder ein neuer Thread oder schon tot.

Folgendes Bild illustriert diesen Zyklus:

Erzeugung eines Threads

Wir hatten gesehn, daß ein Thread wie jedes andere Objekt auch instanziiert wird (mit dem new-Operator). Nach dieser Instanziierung ist der Thread im Zustand eines neuen Threads, belegt noch keine System-Ressourcen und kann außer gesterted zu werden nicht benutzt werden. Wird dies dennoch versucht entsteht ein IllegalThreadStateException-Fehler.

Starten eines Threads

Wie aus den vorangegangenen Beispielen ersichtbar, wurden die verschiedenen Threads mit der Methode start() gestartet, diese ruft die Methode run() des Threads auf. Jetzt erst werden die Systemressourcen angelegt und die Ausführung beginnt.

Eine Thread in den nicht-laufenden Zustand bringen

Ein Thread stoppt seine Ausführung wenn:

Entsprechend kann ein Thread nur dann in den laufenden Zustand gebracht werden, wenn der inverse Weg beschritten wird. D.h.

Stoppen eines Threads

Ein Thread kann und sollte nicht einfach gestoppt werden. Normalerweise richtet man für ihn seinen eigenen Tot ein, indem die run-Methode auf natürliche Art und Weise verlassen wird. Im ersten Beispiel passierte das indem die Schleife ein Ende fand, im zweiten Fall, indem die Referenz zum Thread fallengelassen wurde, und der Thread spätestens vom Garbage Collector vernichtet wird.

Übungen

Synchronisation von Threads

Wir hatten bis jetzt nur unabhängige Threads, welches sich unter anderem für variable Zeiträume zum schlafen legen konnte, aber untereinander keine Daten auszutauschen hatten.

Nun ist es aber in der Regel der Fall, daß ein Thread von den Daten, die ein anderer produzieren kann abhängig. D.h. die Threads müssen sich untereinander "absprechen" um keine Miß-lesungen zu machen.

Dies ist ein komplexes Thema, welches außer Software-Mechanismen wie Object-locking (dank des synchronized Modificators) oder Semaphoren (Jeder Thread ist sein eignener Semaphor, das Auslösen erfolgt durch die wait-Methode auch viel Designphilosophie einschließt um Probleme wie Dead-Locks (alle Threads manövrieren sich in einen Zustand, bei dem alle auf alle warten und nichts mehr passiert)

timer

Alternatif zu der direkten Benutzung von Threads, stehen in der Swing-Bibliothek die Timer zur Verfügung, diese implementieren eine Art Semaphor, wobei nach jedem vorgegebenen Intervall ein AktionEvent ausgelöst wird. Demnach nimmt der Konstruktor eine Zeitspanne une einen aktionListener als Argumente.

Der Timer wird mit start() gestartet, und mit stop() gestoppt.

Übung

Das vorhergehende Schneckenrennen mit Timern implementieren.
Inhaltsverzeichnis API Einführung Java Entwicklungs-Kit Erste Programme Java-Grundwissen Erste Objekte Ein/Ausgabe in Java Oft benutzte Java-Werkzeuge Graphische Oberfläche Applets Threads Netzwerk Das TopDraw-Projet

Document mit wml erzeugt von Bruno Böttcher unter Benutzung von öffentlichen Dokumenten.