Galileo Computing < openbook >
Galileo Computing - Professionelle Buecher. Auch fuer Einsteiger.
Galileo Computing - Professionelle Buecher. Auch fuer Einsteiger.


Java ist auch eine Insel von Christian Ullenboom
Buch: Java ist auch eine Insel (Galileo Computing)
gp Kapitel 9 Threads und nebenläufige Programmierung
gp 9.1 Prozesse und Threads
gp 9.1.1 Wie parallele Programme die Geschwindigkeit steigern können
gp 9.2 Threads erzeugen
gp 9.2.1 Threads über die Schnittstelle Runnable implementieren
gp 9.2.2 Threads über Runnable starten
gp 9.2.3 Die Klasse Thread erweitern
gp 9.2.4 Erweitern von Thread oder Implementieren von Runnable?
gp 9.3 Threads schlafen
gp 9.3.1 Eine Zeituhr
gp 9.4 Die Klassen Timer und TimerTask
gp 9.5 Die Zustände eines Threads
gp 9.5.1 Das Ende eines Threads
gp 9.5.2 Einen Thread höflich mit Interrupt beenden
gp 9.5.3 Der stop() von außen
gp 9.5.4 Das ThreadDeath-Objekt
gp 9.5.5 Auf das Ende warten mit join()
gp 9.6 Arbeit niederlegen und wieder aufnehmen
gp 9.7 Priorität
gp 9.7.1 Threads hoher Priorität und das AWT
gp 9.7.2 Granularität und Vorrang
gp 9.8 Dämonen
gp 9.9 Kooperative und nichtkooperative Threads
gp 9.10 Synchronisation über kritische Abschnitte
gp 9.10.1 Gemeinsam genutzte Daten
gp 9.10.2 Probleme beim gemeinsamen Zugriff und kritische Abschnitte
gp 9.10.3 Punkte parallel initialisieren
gp 9.10.4 i++ sieht atomar aus, ist es aber nicht
gp 9.10.5 Abschnitte mit synchronized schützen
gp 9.10.6 Monitore
gp 9.10.7 Synchronized-Methode am Beispiel der Klasse StringBuffer
gp 9.10.8 Synchronisierte Blöcke
gp 9.10.9 Vor- und Nachteile von synchronisierten Blöcken und Methoden
gp 9.10.10 Nachträglich synchronisieren
gp 9.10.11 Monitore sind reentrant, gut für die Geschwindigkeit
gp 9.10.12 Deadlocks
gp 9.10.13 Erkennen von Deadlocks
gp 9.11 Variablen mit volatile kennzeichnen
gp 9.12 Synchronisation über Warten und Benachrichtigen
gp 9.12.1 Falls der Lock fehlt: IllegalMonitorStateException
gp 9.12.2 Warten mit wait() und Aufwecken mit notify()
gp 9.12.3 Mehrere Wartende und notifyAll()
gp 9.12.4 wait() mit einer Zeitspanne
gp 9.12.5 Beispiel Erzeuger-Verbraucher-Programm
gp 9.12.6 Semaphoren
gp 9.12.7 Die Concurrency Utilities von Doug Lea
gp 9.13 Aktive Threads in der Umgebung
gp 9.14 Gruppen von Threads in einer Thread-Gruppe
gp 9.14.1 Etwas über die aktuelle Thread-Gruppe herausfinden
gp 9.14.2 Threads in einer Thread-Gruppe anlegen
gp 9.14.3 Methoden von Thread und ThreadGroup im Vergleich
gp 9.15 Einen Abbruch der virtuellen Maschine erkennen


Galileo Computing

9.14 Gruppen von Threads in einer Thread-Gruppedowntop

Wenn wir einen Thread erzeugen, gehört dieser automatisch zu einer Gruppe, die durch ein ThreadGroup-Objekt repräsentiert wird. Die Verwaltung ist Aufgabe der Laufzeitumgebung. Die Gruppenzugehörigkeit bekommt jeder Thread bei seiner Erzeugung zugeteilt, und sie kann von uns beeinflusst werden. Entweder geben wir die Gruppe explizit an, dafür gibt es einen passenden Konstruktor, oder der Thread wird automatisch der Gruppe des Threads zugeordnet, der ihn erzeugt hat. Da Thread-Gruppen baumartig organisiert sind, kann jede Thread-Gruppe wiederum weitere Untergruppen besitzen. Die Wurzel dieses Baums bildet für Benutzer-Threads die Gruppe main. Über diese Gruppenzugehörigkeit lassen sich Threads leicht verwalten, da sich beispielsweise alle Threads einer Gruppe gleichzeitig stoppen lassen oder die Priorität geändert werden kann. Von Applets erzeugte Threads gehören automatisch zu speziellen Untergruppen, so dass diese die Laufzeitumgebung nicht negativ beeinflussen.

Abbildung
Hier klicken, um das Bild zu Vergrößern


Galileo Computing

9.14.1 Etwas über die aktuelle Thread-Gruppe herausfindendowntop

Das folgende Programm nutzt die Methode getThreadGroup(), um das ThreadGroup-Objekt des aktuellen Threads zu bekommen. Anschließend verwendet es getParent(), um die Thread-Gruppen ebenenweise nach oben zu durchlaufen und somit das Vater-Objekt für alle Threads auszulesen.

Listing 9.17 ShowThreadsInMain.java

public class ShowThreadsInMain
{
  public static void main( String args[] )
  {
    ThreadGroup top = Thread.currentThread().getThreadGroup();
    while ( top.getParent() != null )
      top = top.getParent();
    showGroupInfo( top );
  }
  public static void showGroupInfo( ThreadGroup group )
  {
    Thread threads[] = new Thread[ group.activeCount() ];
    
    group.enumerate( threads, false );
    System.out.println(group);
    
    for ( int i = 0; i < threads.length; i++ )
      if ( threads[i] != null )
  
        System.out.println( group.getName() + " -> " + threads[i] + " is" + 
                            isDeamon(threads[i]) + " Daemon ");
    ThreadGroup activeGroup[] = new ThreadGroup[ group.activeGroupCount() ];
    group.enumerate( activeGroup, false );
    for ( int i = 0; i < activeGroup.length; i++ )
      showGroupInfo( activeGroup[i]);
  }
  private static String isDeamon( Thread t )
  {
    return t.isDaemon() ? "" : " no";
  }
}

Dann wird mit der schon bekannten enumerate()-Methode eine Liste erstellt und eine Auflistung aller Untergruppen auf den Bildschirm ausgegeben. Die Anzahl der Threads in der Gruppe ergibt sich mit activeCount(). activeCount() und enumerate() beziehen sich hier auf Objektmethoden einer Thread-Gruppe und nicht mehr auf Klassenmethoden von Thread.

java.lang.ThreadGroup[name=system,maxpri=10]
system -> Thread[Reference Handler,10,system] is Daemon 
system -> Thread[Finalizer,8,system] is Daemon 
system -> Thread[Signal Dispatcher,10,system] is Daemon 
system -> Thread[CompileThread0,10,system] is Daemon 
java.lang.ThreadGroup[name=main,maxpri=10]
main -> Thread[main,5,main] is no Daemon 

class java.lang.Thread
implements Runnable

gp static Thread currentThread()
Liefert eine Referenz auf den aktuell laufenden Thread.
gp final ThreadGroup getThreadGroup()
Liefert die Thread-Gruppe, zu der der Thread gehört. Wenn es den Thread schon nicht mehr gibt, liefert die Methode null.

class java.lang.ThreadGroup

gp final ThreadGroup getParent()
Liefert die Obergruppe der Thread-Gruppe.
gp int activeCount()
Liefert die Anzahl aktiver Threads in der Gruppe inklusive aller Untergruppen.
gp int activeGroupCount()
Liefert die Anzahl aktiver Untergruppen in der Gruppe.
gp int enumerate( Thread threadList[] )
Kopiert eine Referenz auf jeden aktiven Thread der Gruppe und auf alle Threads in ihren Untergruppen in das Array.
gp int enumerate( Thread threadList[], boolean recurse )
Kopiert eine Referenz auf jeden aktiven Thread der Gruppe in das Array. Ist recurse gleich true, so werden auch Referenzen auf die Threads der Untergruppen mitkopiert.
gp int enumerate( ThreadGroup groupList[] )
Kopiert Referenzen auf die Untergruppen, die mindestens einen aktiven Thread enthalten, inklusive aller Unteruntergruppen in das Array.
gp int enumerate( ThreadGroup groupList[], boolean recurse )
Kopiert Referenzen auf jede aktive Untergruppe inklusive aller Unteruntergruppen, wenn recurse gleich true ist.

Ein Array der passenden Größe müssen wir zunächst anlegen. Dies wird am besten mit activeCount() gemacht. Ist das Array für die Threads zu klein, werden die überzähligen Threads nicht mehr in das Array kopiert.


Galileo Computing

9.14.2 Threads in einer Thread-Gruppe anlegendowntop

Wollen wir Threads in einer Gruppe anlegen, dann müssen wir zunächst ein ThreadGroup-Objekt erzeugen. Dazu bietet die Klasse zwei Konstruktoren. Im ersten müssen wir lediglich den Namen der Gruppe angeben. Da eine Thread-Gruppe, die mehrere Threads zusammenfasst, wiederum Mitglied eines ThreadGroup-Objekts sein kann, existiert ein zweiter Konstruktor, der die übergeordnete Gruppe bestimmt. Ohne diese Angabe wird die Thread-Gruppe des aktuellen Threads zum Vater der neuen Gruppe.


class java.lang.ThreadGroup

gp ThreadGroup( String name )
Erzeugt eine Thread-Gruppe mit dem Namen name.
gp ThreadGroup( ThreadGroup parent, String name )
Erzeugt eine neue Thread-Gruppe, deren Vater parent ist.

Nun haben wir die Gruppe angelegt, jetzt fehlen uns noch die Threads. Diese können nur bei ihrer Erzeugung in die Gruppe aufgenommen werden. Dafür bietet uns die Thread-Klasse spezielle Konstruktoren, um den neuen Thread einer bestimmten Gruppe hinzuzufügen. Folgende Zeilen reichen dafür aus, um die drei Comic-Helden in eine Gruppe zu stecken:

ThreadGroup group = new ThreadGroup( "Disney Family" );
Thread thread1 = new Thread( group, "Donald" );
Thread thread2 = new Thread( group, "Daisy" );
Thread thread2 = new Thread( group, "Micky" );

Hinweis Die Zugehörigkeit eines Threads zu einer Gruppe lässt sich nachträglich nicht mehr ändern.


class java.lang.Thread
implements Runnable

gp Thread( ThreadGroup parent, Runnable target, String name )
Erzeugt ein neues Thread-Objekt mit dem Namen name in der Gruppe group und dem Runnable-Objekt target, welches die auszuführende run()-Methode enthält. Ist die Gruppe null, so wird der Thread in der Gruppe erzeugt, in der auch der erzeugende Thread liegt. Ist target gleich null, so enthält das Thread-Objekt selbst die run()-Methode.
gp Thread( ThreadGroup parent, Runnable target )
Erzeugt ein neues Thread-Objekt. Der Konstruktor verweist auf Thread(parent, target, "Thread-"+n), wobei n eine Ganzzahl ist, die mit nextThreadNum() erfragt wird. Bei jedem erzeugten Thread wird n inkrementiert.
gp Thread( ThreadGroup group, String name )
Erzeugt ein neues Thread-Objekt in der Gruppe group. Entspricht dem Konstruktor Thread(group, null, name).

Beispiel Erstellung und Nutzung einer eigenen Thread-Gruppe

Listing 9.18 ThreadInThreadGroup.java

public class ThreadInThreadGroup
{
  static public void main( String args[] )
  {
    ThreadGroup group = new ThreadGroup( "Helden" );
    Thread t1 = new OwnThread( group, "Darkwing Duck" );
    Thread t2 = new OwnThread( group, "Kikky" );
    t1.start(); t2.start();
    Thread threadArray[] = new Thread[ group.activeCount() ];
    // Array mit allen Threads der Gruppe group füllen
    group.enumerate( threadArray );
    // Array ausgeben
    for ( int i = 0; i < threadArray.length; i++ )
      System.out.println( threadArray[i] );
    group.list();
   }
}
class OwnThread extends Thread
{
  public OwnThread( ThreadGroup group, String name ) {
    super( group, name );
  }
  public void run()
  {
    for ( int i = 0; i < 3; i++ )
    {
      System.out.println( getName() + ": Ich bin der Schrecken, "+
                          "der die Nacht durchflattert" );
    }
  }
}

Läuft das Programm, produziert es eine Ausgabe folgender Art:

Thread[Darkwing Duck,5,Helden]
Thread[Kikky,5,Helden]
java.lang.ThreadGroup[name=Helden,maxpri=10]
    Thread[Darkwing Duck,5,Helden]
    Thread[Kikky,5,Helden]
Darkwing Duck: Ich bin der Schrecken, der die Nacht durchflattert
Kikky: Ich bin der Schrecken, der die Nacht durchflattert
Darkwing Duck: Ich bin der Schrecken, der die Nacht durchflattert
Kikky: Ich bin der Schrecken, der die Nacht durchflattert
Darkwing Duck: Ich bin der Schrecken, der die Nacht durchflattert
Kikky: Ich bin der Schrecken, der die Nacht durchflattert

Wesentlich einfacher ist die Testausgabe einer Thread-Gruppe mit der Objektmethode list(). Kürzer lässt sich der Programmcode in main() für die ThreadInThreadGroup dann so schreiben:

ThreadGroup group = new ThreadGroup( "Helden" );
OwnThread t1 = new OwnThread( group, "Darkwing Duck" );
OwnThread t2 = new OwnThread( group, "Kikky" );
group.list();

Die list()-Methode macht nur zu Testzwecken Sinn.


class java.lang.ThreadGroup

gp void list()
Listet Informationen über die Thread-Gruppe auf die Standardausgabe.

Galileo Computing

9.14.3 Methoden von Thread und ThreadGroup im Vergleichtoptop

Viele der Methoden, die ein Thread besitzt, können auch auf Gruppen übertragen werden. Die Operationen gelten dann aber für alle Threads der Gruppe und nicht für einen individuellen Thread. So lässt sich etwa die Priorität aller in der Gruppe befindlichen Threads mit folgender Zeile auf das Maximum setzen:

group.setMaxPriority( Thread.MAX_PRIORITY );

MAX_PRIORITY ist in der Klasse Thread definiert und hat den Wert 10. Genauso lassen sich NORM_PRIORITY (=5) und MIN_PRIORITY (=1) zuweisen. Ist die Priorität eines Threads schon größer als der zu setzende Wert - was bei MAX_PRIORITY der Fall wäre -, wird der Thread in seiner Priorität nicht heruntergestuft. Damit macht die Funktion setMaxPriority() etwas anderes als ein individuelles setPriority() für jeden Thread der Gruppe mit diesem Prioritätswert.


class java.lang.ThreadGroup

gp final void setMaxPriority( int priority )
Setzt die Priorität der Threads auf mindestens priority.
gp final int getMaxPriority()
Liefert die größte Priorität, die ein Thread in der Gruppe besitzt.

Veraltete Methoden und Ähnliches

Einige Methoden sind veraltet und sollten nicht mehr verwendet werden. Dazu zählen allowThreadSuspension(boolean), resume(), stop() und suspend(). Außer allowThreadSuspension() sind die anderen Methoden auch in der Klasse Thread veraltet. Immer noch aktuell und nützlich sind die Funktionen destroy() und interrupt().

gp final void destroy()
Entfernt die Thread-Gruppe und alle Untergruppen. Die darin enthaltenen Threads müssen beendet worden sein. Eine IllegalThreadStateException wird ausgelöst, wenn die Gruppe nicht leer ist oder wenn die Gruppe schon entfernt wurde.
gp final void interrupt()
Unterbricht alle Threads in der Gruppe und den Untergruppen.
gp final void setDaemon( boolean daemon )
Ändert den Status der Thread-Gruppe. Diese Gruppeneigenschaft ist orthogonal zur Dämon-Eigenschaft einzelner Threads. Die Dämon-Gruppe wird entfernt, falls der letzte Thread stoppt oder die letzte Thread-Gruppe entfernt wurde.
gp final boolean isDaemon()
Testet, ob die Thread-Gruppe eine Dämon-Gruppe ist. Eine Dämon-Thread-Gruppe wird automatisch entfernt, wenn der letzte Thread gestoppt oder zerstört wurde.

Kein join in einer Thread-Gruppe

Eine Thread-Gruppe kennt keine join()-Funktion wie ein Thread, so dass es über eine einzelne Funktion nicht möglich ist, das Ende aller Threads in einer Gruppe zu erkennen. Doch die Thread-Gruppe besitzt die nützliche Funktion activeCount(), über die etwa die Anzahl aktiver Threads in Erfahrung gebracht werden kann. Damit ist Folgendes denkbar:

void join( ThreadGroup tg )
{
  synchronzied( tg )
  {
    while ( tg.activeCount() > 0 )
      tg.wait( 10 );
  }
}

Das alles kommt noch in einen sychronized-Block und in ein wait(), damit die Schleife nicht zu viel Zeit kostet. Unter Umständen ist auch noch eine Abfrage von tg.isDestroyed() vorher sinnvoll.





Copyright (c) Galileo Press GmbH 2004
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Galileo Computing]

Galileo Press GmbH, Gartenstraße 24, 53229 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de