Angelika Langer - Training & Consulting
HOME | COURSES | TALKS | ARTICLES | GENERICS | LAMBDAS | IOSTREAMS | ABOUT | NEWSLETTER | CONTACT | Twitter | Lanyrd | Linkedin
 
HOME 

  OVERVIEW

  BY TOPIC
    JAVA
    C++

  BY COLUMN
    EFFECTIVE JAVA
    EFFECTIVE STDLIB

  BY MAGAZINE
    JAVA MAGAZIN
    JAVA SPEKTRUM
    JAVA WORLD
    JAVA SOLUTIONS
    JAVA PRO
    C++ REPORT
    CUJ
    OTHER
 

GENERICS 
LAMBDAS 
IOSTREAMS 
ABOUT 
NEWSLETTER 
CONTACT 
Java Multithread Support - Anhalten und Beenden von Threads

Java Multithread Support - Anhalten und Beenden von Threads
Java Multithread Support
Anhalten und Beenden von Threads

JavaSPEKTRUM, November 2004
Klaus Kreft & Angelika Langer

Dies ist das Manuskript eines Artikels, der im Rahmen einer Kolumne mit dem Titel "Effective Java" im JavaSPEKTRUM erschienen ist.  Die übrigen Artikel dieser Serie sind ebenfalls verfügbar ( click here ).

 

Nachdem wir in unserem letzten Artikel (/ KRE5 /) einen Klassiker der Java Multithread-Programmierung, das Nested-Monitor-Problem, diskutiert haben, wenden wir uns in diesem Artikel einer anderen klassischen Java Multithread-Fragestellung zu: Wie kann man Threads in Java anhalten oder beenden? Zu diesem Thema gehört unter anderem: Warum sollte man Thread.stop() oder Thread.suspend() nicht benutzen, obwohl „wir bis jetzt noch keine Probleme damit auf Sun’s JVM für Windows hatten“ (Zitat aus einem Projekt)? Was hat es mit der InterruptedException auf sich, die unter anderem von den Methoden Object.wait() bzw. Condition.await() geworfen werden kann und deren Diskussion wir bisher immer wieder ausgeklammert haben?
 

Die Geschichte von Thread.stop() und Thread.suspend()

Seit dem JDK 1.0 gibt es in der Klasse Thread die beiden public Methoden start() und stop(). Die Methode start() startet den Thread und bringt die Methode run() zur Ausführung. Die Methode stop() dient nicht etwa dazu den Thread zu stoppen, sondern beendet ihn. Zum Stoppen (im Sinne von Anhalten) und Weiterlaufen sind die Methoden suspend() und resume() vorgesehen. Alle vier Methoden sind public Instanzmethoden. Sie können von Außen auf einem Thread-Objekt aufgerufen werden, um den Ablaufzustand eines Threads zu ändern (siehe auch Zustandsübergangsdiagramm in Abbildung 1).


Abbildung 1:Zustände eines Threads




Mit dem JDK 1.1 wurden stop(), suspend() und resume()) deprecated. Wenn eine JDK Methode als deprecated gekennzeichnet wird, dann bedeutet dies: die Methode soll nicht mehr benutzt werden. Um existierenden Code nicht zu brechen, bleibt die Methode aber im JDK API, selbst wenn ihre Benutzung mit Problemen verbunden sein kann. Und die Benutzung von stop()und suspend() kann mit Problemen verbunden sein.  Warum?

Dazu erinnern wir uns noch einmal an die Überlegungen aus unseren ersten beiden Artikeln über Multithread-Programmierung (siehe /KRE1/ und /KRE2/). Wir haben damals darüber diskutiert, dass ein synchronized Block (bzw. eine synchronized Methode) einen kritischen Bereich bildet, der von anderen Threads wie eine atomare Operation gesehen wird. Er dient dazu, die Attribute eines Objekts konsistent zu ändern. Als Beispiel haben wir einen Stack implementiert, der int Werte speichern kann:

public class IntStack {
 private final int[] array;
 private volatile int cnt = 0;

 public IntStack (int sz) { array = new int[sz]; }

 synchronized public void push (int elm) {
   if (cnt < array.length) array[cnt++] = elm;
   else throw new IndexOutOfBoundsException();
 }

 synchronized public int pop () {
   if (cnt > 0) return(array[--cnt]);
   else throw new IndexOutOfBoundsException();
 }

 synchronized public int peek() {
   if (cnt > 0) return(array[cnt-1]);
   else throw new IndexOutOfBoundsException();
 }

 public int size() { return cnt; }

 public int capacity() { return (array.length); }
}

Sehen wir uns noch einmal die pop() Methode an. Sie ist synchronized, damit sowohl die Abfrage des Stackpointers cnt sowie seine Veränderung und Nutzung als auch der Zugriff auf das interne Arrays array (in array[--cnt]) atomar sind und nicht von einem anderen Thread unterbrochen werden können.

Was wäre nun, wenn stop() auf dem Thread aufgerufen wird, der gerade die pop() Methode ausführt? Der Thread wird mitten in dem kritischen Bereich (denn die ganze Methode ist ja synchronized) unterbrochen. Da der Thread beendet wird, werden alle Mutexsperren, die von dem Thread gehalten werden, automatisch freigegeben. Das macht auch Sinn, denn andernfalls würden diese Objekte für alle weiterhin existierenden Threads gesperrt bleiben. Nehmen wir an, die Unterbrechung durch stop() erfolgt im Statement: return array[--cnt] und zwar nachdem der Stackpointer cnt dekrementiert wurde, aber bevor der Wert aus dem array herausgelesen und zurückgegeben wurde. Durch die Unterbrechung ist der oberste Eintrag im Stack für immer verschwunden.

Abstrakt betrachtet besteht das Problem darin, dass ein Thread innerhalb eines kritischen Bereichs beendet werden kann. Dies kann wiederum dazu führen, dass das gerade manipulierte Objekt inkonsistent wird. Wenn dieses Objekt dann weiterhin von anderen Threads zugreifbar ist, wirkt sich die Inkonsistenz auch auf den Rest des Programms aus.

Bevor wir uns die Alternativen zu Thread.stop() für das Beenden eines Threads ansehen, schauen wir, was mit Thread.suspend() nicht in Ordnung ist. Man könnte jetzt vermuten, dass die Situation bei suspend() eine ähnliche wie bei stop() ist. Aber dem ist nicht so. Im Gegensatz zu stop() hebt suspend() nämlich die Mutexsperren des Threads nicht auf. Aber das kann auch ein Problem sein. Schauen wir uns dazu ein Beispiel mit unserem IntStack an. Nehmen wir an, wir haben drei Threads, die auf ein IntStack Objekt mit Kapazität 1000 Zugriff haben. Ein Thread ist der Produzent. Er trägt mit push() die Werte in den Stack ein. Ein anderer Thread ist der Konsument. Er holt mit pop() die Werte aus dem Stack heraus. Der dritte Thread übernimmt die Flusskontrolle. Er hält den Produzenten mit suspend() an, wenn mehr als 800 Werte im Stack sind, und startet ihn wieder mit resume(), wenn es weniger als 500 sind. Um den Füllstand des Stacks zu ermitteln, ruft er die Methode size() des Stacks alle zwei Sekunden auf. Dieser Pollmechanism ist zwar nicht besonders elegant, aber für unser Beispiel reicht er uns.

Im praktischen Test wird unser System immer wieder sporadisch vollständig hängen. Das wird immer dann sein, wenn der suspend()-Aufruf durch den Flusskontroll-Thread den Produzenten-Thread so trifft, dass dieser im push() angehalten wird. Während seiner Wartezeit hält der Produzenten-Thread weiter das Mutex, das mit seinem Eintritt in push() gesperrt wurde. Leider bleibt dadurch der Produzent beim nächsten Aufruf vom pop() hängen. Er bekommt die Sperre des Mutex nicht. Der Inhalt des Stacks kann deshalb nicht mehr verändert werden. Das hat zur Folge, dass der Flusskontroll-Thread stets denselben hohen Füllstand beim Aufruf von size() zurückbekommt und somit das resume() nie wieder auf dem Produzenten-Thread aufrufen wird. Diese Stillstand-Situation kann nur durch Neustart wieder behoben werden. Das Ganze tritt nur sporadisch auf, weil der Konsumenten-Thread nicht nur push() aufruft, sondern auch die Werte erzeugen muss. Trifft ihn das suspend() in einer solchen Situation, dann entstehen keine Probleme.

Es gab noch einen weiteren kleinen Haken mit der Benutzung von suspend() und resume(). Ein resume(), das auf einem Thread aufgerufen worden ist, der nicht suspendiert ist, geht für immer verloren. Das heißt, in Situationen, in denen es zu sogenannten Race Conditions kommen kann (d.h. es ist von der Programmlogik nicht sichergestellt, ob erst suspend() oder resume() aufgerufen wird), können diese Methoden nicht benutzt werden.

All diese Überlegungen haben dann dazu geführt, dass suspend(), resume() und stop() zum JDK1.1 deprecated geworden, weil man mittlerweile schlauer geworden war.
 

Welche Alternative gibt es zu Thread.stop() und Thread.suspend()

Schaut man heute (z.B. JDK 1.4.2 oder 1.5) auf die Instanzmethoden der Klasse Thread, so hat man das Gefühl, dass es keinen Ersatz für stop() und suspend() gibt. Dies hat häufig zur Folge, dass es ziemlich verlockend ist, diese beide Methoden weiterhin zu benutzen. Das führt dann zu Fehlern, wie wir sie oben diskutiert haben. Die Praxis zeigt zwar meistens, dass diese Fehler eher selten auftreten (siehe Zitat zu Anfang dieses Artikels: „wir hatten bis jetzt noch keine Probleme damit auf Sun’s JVM für Windows“). Trotzdem sollte man den Verlockungen wiederstehen.

Zugegeben, die Unterstützung durch den JDK bei der Nutzung von Alternativen zu stop() und suspend() ist nicht besonders umfangreich. Aber das hat seine Ursachen in dem grundsätzlichen Ansatz der Alternativen. Das Problem von stop() und suspend() ist im wesentlichen, dass diese Methoden den Zustands des Threads verändern können, während er in einem synchronized Block (bzw. einer synchronized Methode) ist. Das bedeutet, dass er gerade eine Reihe von Statements ausführt, die nach Außen atomar sein müssen. Wie wir gesehen haben, ist es weder eine gute Idee, ihn dabei zu unterbrechen und die Sperre freizugeben (wie beim stop()), noch ihn zu unterbrechen und die Sperre zu erhalten (wie beim suspend()). Besser wäre es doch, wenn die Unterbrechung des Threads dann erfolgte, wenn er gerade nicht in einem synchronized Block (bzw. einer synchronized Methode) ist. Es könnte sogar so funktionieren, dass der Auftrag für die Unterbrechung immer angenommen werden kann, die eigentliche Unterbrechung des Threads aber erst dann erfolgt, wenn der Thread gerade keine Sperren hält. Es gibt aber keine neuen Methoden des Threads APIs, die das für einen tun. Stattdessen ist die Philosophie, dass der Thread (genauer gesagt der Thread-Code) es am besten selbst weiß, wenn er in keinem kritischen Bereich ist. Dann kann er sich selbst beenden oder warten, falls er von Außen eine Aufforderung dazu bekommen hat. Der Mechanismus, solche Aufforderungen an einen Thread zuzustellen, ist neu zum JDK 1.1 API dazugekommen, als stop() und suspend() weggefallen (bzw. deprecated erklärt worden) sind. Wir schauen uns den neuen Mechanismus für Thread-Unterbrechungen im folgenden an.

Thread Unterbrechungen

Seit dem JDK 1.1 hat jeder Thread einen Unterbrechungsanforderungszustand (kurz: Interrupt-Status). Die Methode Thread.interrupt() nimmt die Anforderung zur Unterbrechung an einen Thread entgegen und setzt den Unter- brechungsanforderungszustand entsprechend. Sie ist eine public Instanzmethode, die von Außen auf dem Thread-Objekt aufgerufen werden kann.

Die Art und Weise, wie die Unterbrechungsanforderung an einen Thread zugestellt wird, ist abhängig von der Methode, die der Thread gerade ausführt. Im Normalfall ist es Aufgabe des jeweiligen Threads, regelmäßig die Methode Thread.interrupted() aufzurufen.  interrupted() ist eine statische Methode, die den Anforderungszustand des jeweils aktuellen Threads zurückliefert.  Sie liefert true zurück, wenn eine Unterbrechungsanforderung an den aktuellen Thread gestellt wurde.

Dabei ist zu beachten, dass nur einmal true zurückgeliefert wird. Die Methode interrupted() setzt den Anforderungszustand des Threads wieder zurück und bei weiteren Aufrufen von interrupted() wird false zurückgeliefert, bis wieder eine neue Unterbrechungsanforderung mit interrupt() von Außen gestellt wird. Nun könnte man denken, dass pro  Unterbrechungsanforderung  (d.h. pro Aufruf von interrupt()) genau einmal true von der Methode interrupted() zurückgeliefert wird. Das ist aber nicht so.

Wenn bei bereits bestehender Unterbrechungsanforderung wiederholt interrupt() von Außen aufgerufen wird, so führen alle diese Aufrufe nur zur Zurückgabe eines einzigen true, und zwar beim nächsten Aufruf von interrupted(). Das Zustandsdiagramm in Abbildung 2 zeigt dieses Verhalten. Damit interrupt() nicht überflüssigerweise aufgerufen wird, kann von Außen mit der Instanzmethode Thread.isInterrupted() der aktuelle Unterbrechungsanforderungszustand des Threads von jedem geprüft werden, der Zugriff auf das Thread-Objekt hat.


Abbildung 2: Thread-Zustände im Zusammenhang mit Unterbrechungsanforderungen

Threads, die länger wartende Aufrufe wie Object.wait(), Thread.sleep(), Thread.join() oder Methoden aus dem JDK 1.5 wie Lock.lockInterruptibly() oder Condition.await() aufrufen, haben nicht die Möglichkeit regelmäßig interrupted() aufzurufen. Deshalb werfen diese Methoden eine InterruptedException, um anzuzeigen, dass mit interrupt() eine Unterbrechungsanforderung gestellt wurde. Das Werfen der Exception setzt auch den Anforderungszustand zurück, so dass ein nachfolgender Aufruf von interrupted() wieder false liefert, wenn keine erneute Anforderung gestellt wurde.

Im JDK 1.5 gibt es übrigens zusätzlich zu dem Lock.lockInterruptibly() ein Lock.lock(), das nicht von einer Unterbrechungsanforderung an den Thread unterbrochen wird und also auch keine InterruptedException wirft. Dies gilt in gleicher Form auch für das mit einem Objekt assoziierte Mutex, das mit dem Keyword synchronized gesperrt wird. Auch hier wird das Warten auf die Sperre  nicht abgebrochen, wenn der Thread eine Unterbrechungsanforderung erhält. Eine Alternative wie beim expliziten Lock gibt es beim Warten an einem synchronized-Block aber nicht. Die Ergänzung im JDK 1.5 ist hier deutlich flexibler; was übrigens auch für die Condition Abstraktion aus dem JDK 1.5 gilt. Sie bietet sowohl unterbrechbare (await())  wie ununterbrechbare Methoden (awaitUninterruptibly()) an, um auf das Eintreffen der Bedingung zu warten.

Weitere Besonderheiten bei der Reaktion auf Unterbrechungsanforderungen gibt es bei der NIO (New I/O im Package java.nio), die seit der Version 1.4 Bestandteil des JDK ist. Die erste Besonderheit findet man bei den wartenden (englisch: blocking) Ein-/Ausgabeoperation: wenn man eine wartende Ein-/Ausgabeoperation der NIO auf einem InterruptibleChannel aufgerufen hat, so wird diese durch eine Unterbrechungsanforderung mit der ClosedByInterruptException abgebrochen und der Channel geschlossen.  Man bekommt also eine andere Exception als sonst, d.h. nicht die übliche InterruptedException.  Der Anforderungszustand wird nicht zurückgesetzt, d.h. der nächste Aufruf von interrupted() liefert true. Auch das ist anders als beim Abbruch der anderen wartenden oder länger andauernden Methoden wie Object.wait(), Thread.sleep(),  Thread.join()oder  Condition.await(), usw.
Die zweite Besonderheit betrifft die Methode Selector.select(): sie kommt bei einer Unterbrechungsanforderung vorzeitig, aber ohne Exception zurück. Um zu erkennen, dass es sich um eine Unterbrechungsanforderung handelt, muss der Anforderungszustand mit interrupted() abgefragt werden.

Für alle wartenden Ein-/Ausgabeoperationen der klassischen I/O aus dem Package java.io gilt, dass sie durch Unterbrechungsanforderungen nicht beeinflusst werden, d.h es kann ganz normal nach Rückkehr der Methode mit interrupted() der Anforderungszustand geprüft werden.

Zusammenfassend lässt sich sagen, dass das Ausliefern der Unterbrechungsanforderung an den zu unterbrechenden Thread höchst unterschiedlich erfolgen kann, je nachdem ob und welche wartende Methode gerade durch den Thread aufgerufen wurde. Dabei hat jede JDK Erweiterung es jedes Mal besser machen wollen, was zu leichten Inkonsistenten geführt hat: Mal hat die Threadunterbrechung gar keinen Einfluss. Dann wird eine Exception geworfen und der Anforderungszustand zurückgesetzt. Mal wird eine Exception geworfen und der Anforderungszustand nicht zurückgesetzt. Und dann gibt es noch die Möglichkeit, dass die Methode sofort ohne Exception zurückkehrt und der Anforderungszustand selbst überprüft werden muss.

Je nach Konfiguration, können beim Aufruf von interrupt() auch noch Security-Aspekte eine Rolle spielen. Wenn kein SecurityManager vorhanden ist, funktioniert der Aufruf von interrupt() ohne jede Einschränkungen. Wenn ein SecurityManager vorhanden ist, dann wird in der interrupt() Methode die checkAccess(Thread t) Methode des SecurityManagers aufgerufen, wobei als Argument der Thread mitgegeben wird, der unterbrochen werden soll. Im Defaultfall, also bei einem Standard-SecurityManager, prüft die checkAccess Methode, ob der zu unterbrechende Thread ein Systemthread ist, d.h. ob er in der Threadgruppe mit Parent null ist. Wenn der zu unterbrechende Thread ein Systemthread ist, dann wird geprüft, ob der aktuelle Thread die RuntimePermission mit Wert modifyThread bezüglich des zu unterbrechenden Threads hat; wenn der zu unterbrechende Thread ein Userthread ist, wird nichts geprüft.  Dieses Verhalten der checkAccess Methode kann in einem abgeleiteten, benutzerspezifischen SecurityManager redefiniert werden. Dabei empfiehlt die JavaDoc als einzige mögliche Implementierung, die oben beschriebene Security-Policy von den Systemthreads auf alle Threads auszudehnen. Das heißt, bei einem benutzerspezifischen SecurityManager wird auch für Userthreads geprüft, ob die richtige RuntimePermission vorliegt. Immer dann wenn die Methode SecurityManager.checkAccess(Thread t) den Aufruf aus Sicherheitsgründen nicht zulässt, wird interrupt() mit einer SecurityException abgebrochen.

Unterbrechung und was dann ?

Nachdem wir uns ausführlich mit dem Unterbrechungsmechanismus von Threads beschäftigt haben, wollen wir nun schauen, wie man diesen Mechanismus nutzen kann, um einen Thread anzuhalten bzw. zu beenden.

Thread beenden

Fangen wir mit der einfachsten Situation an. Nehmen wir an, wir wollen, dass sich ein Thread beendet, wenn interrupt() auf seiner Threadinstanz aufgerufen wurde. Wenn wir annehmen, dass der Thread in seiner run() Methode eine forever-Schleife hat, die er zyklisch durchläuft, so sollte er in dieser Schleife regelmäßig den Unterbrechungsstatus überprüfen und sich wenn nötig beenden:
public void run() {

 ... Initialisierung ...

 for(;;) {
  if (Thread.interrupted())
   return;

  ... Funktionalität des Threads ...

 }
}

Dabei steht die Abfrage if(Thread.interrupted()) außerhalb eines synchronized Blocks; die notwendigen Synchronisationen verbergen sich in der „... Funktionalität des Threads ...“. Damit soll sichergestellt sein, dass alle Objekte, die währende des Ablaufs des Threads manipuliert werden, in einem konsistenten Zustand sind, wenn der Unterbrechungszustand abgefragt wird. Der Thread kann also im Falle einer bestehenden Unterbrechungsanforderung gefahrlos mit return beendet werden.

Was machen wir mit den Fällen, in denen die Unterbrechungsanforderung durch eine InterruptedExceptions signalisiert wurde? Da der Unterbrechungszustand durch das Zustellen der Exception zurückgesetzt wurde, macht ein Aufruf von interrupted() keinen Sinn; er liefert ohnehin immer false. Aus diesem Grund ist es sinnvoll, im catch-Block einer InterruptedException wieder interrupt() zu rufen, damit unsere bereits in der forever-Schleife vorhandene Abfrage weiterhin funktioniert. Das könnte so aussehen:

...
try {
 sleep(5000);
} catch (InterruptedException ie) {
  Thread.currentThread().interrupt();
}
...
Natürlich könnte man auch ein eigenes internes Flag nutzen, das im catch-Block gesetzt wird und zusätzlich zu interrupted() in der forever-Schleife abgefragt wird. Im allgemeinen ist dies aber nicht nötig.

Da die Unterbrechungsanforderung als Aufforderung zur Beendigung des Threads angesehen wird, empfiehlt es sich, vor jeder langandauernden Aktion im Thread erst einmal zu prüfen, ob eine Unterbrechungsanforderung vorliegt. Wenn ja, dann wird man die längeren Aktionen gar nicht erst anstoßen. Beispielsweise wird man bei vorliegenden Unterbrechungsanforderung keine wartenden (d.h. blocking) Methoden mehr aufrufen. Aufwändige Verarbeitungen innerhalb des Threads würde man ebenfalls im Falle einer bestehenden Unterbrechungsanforderung überspringen. Das gleiche gilt insbesondere auch für Methoden, die nicht auf Grund der Unterbrechungsanforderung sofort abgebrochen werden. Ein Beispiel für eine solche Methode ist die read() Methode auf dem InputStream eines Sockets in der klassichen I/O; die read() Methode kehrt erst dann zurück, wenn sie mit dem Lesen fertig ist, ganz egal ob zwischendurch eine Unterbrechungsaufforderung eingegangen ist oder nicht. Mit der folgenden Abfrage kann man den Aufruf von read() ggf. unterdrücken:

...
if (!Thread.currentThread().isInterrupted())
 in.read(myBuffer);
...
Die Abfrage des Unterbrechungszustands erfolgt am besten mit isInterrupted(), weil isInterrupted() den Unterbrechungszustand wirklich nur abfragt, nicht aber ändert, während interrupted() den Unterbrechungszustand sofort zurücksetzt.
 

Thread anhalten

Es ist etwas aufwändiger, die Funktionalität zu implementieren, die es ermöglicht, einen Thread anzuhalten und weiterlaufen zu lassen. Dazu werden wir den Synchronisationsmechanismus von wait() und notify() nutzen. Das heißt, das Warten des Threads wird durch einen Aufruf von wait() realisiert und die Aufforderung zum Weiterlaufen durch den Aufruf von notify(). Die Implementierung eines solchen Threads könnte zum Beispiel so aussehen:
public class MyThread extends Thread {

 private volatile boolean isWaiting = false;

 public void run() {

  ... Initialisierung ...

  for(;;) {
   while (interrupted() || isWaiting) {
    isWaiting = true;
    try {
     synchronized(this) {
      wait();
     }
     isWaiting = false;
    } catch (InterruptedException) {}
   }

   ... Funktionalität des Threads ...

  }
 }

 public void suspendThread() {
  interrupt();
 }

 public synchronized void resumeThread() {
  notify();
 }

   public boolean isSuspended () {
  return isWaiting;
 }
}

Die Methode resumeThread() ist nur deshalb synchronized, damit notify() aufgerufen werden kann. Ähnliches gilt für den synchronized Block um wait(). Beides sind also keine richtigen kritischen Bereiche, in denen konsistente Datenmanipulationen stattfinden soll.

Befremdlich mag auf den ersten Blick die while()-Schleife um das wait() in der run() Methode erscheinen. Sie dient zusammen mit dem Flag isWaiting dazu, den Thread in ein erneutes wait() zu bringen, falls eine InterruptedException das wait() abgebrochen hat. Das könnte passieren, wenn während des Wartens interrupt() erneut aufgerufen wurde (z.B. weil suspendThread() erneut aufgerufen wurde). Bei einer erneuten Unterbrechungsaufforderung soll der Thread aber weiter warten und nicht plötzlich aufwachen.  isWaiting wird deshalb erst dann auf false gesetzt, wenn das wait() regulär durch ein notify() aus der Methode resumeThread() verlassen wird. Das isWaiting Flag können wir zusätzlich in der Methode isSuspended() nutzen, damit von Außen abgefragt werden kann, ob der Thread im Wartezustand ist.

Unabhängig davon, ob wir interrupt() als Aufforderung zum Beenden oder als Aufforderung zum Anhalten des Thread interpretieren, gilt nach wie vor die schon diskutierten Regeln: Im Thread manipulierte Objekte müssen vor dem Aufruf von wait() in einem konsistenten Zustand sein. Der Unterbrechungsstatus muss in catch-Blöcken, in denen InterruptedExceptions gefangen wurden, wieder gesetzt werden. Teile des Code können bei Bedarf mit der Abfrage if (!Thread.currentThread().isInterrupted()) übersprungen werden.

Weitere Fälle

Im obigen Beispiel haben wir diskutiert, wie man einen Thread implementieren kann, der interrupt() als Aufforderung zum Anhalten interpretiert.  Man kann die Threadunterbrechung auch benutzen, um den Thread sowohl anzuhalten als auch zu beenden (indem man etwa eine stopThread() hinzufügt).  Um ein solches Verhalten zu implementieren, bedarf es einer weiteren Instanzvariable, um die Details der Anforderung (Beenden oder Anhalten) zu kommunizieren. Die Implementierungsdetails ergeben sich relativ einfach aus den oben bereits diskutierten Beispielen.

Wenn man in diesem Fall sowieso eine Variable für die Anforderungsdetails braucht, könnte man auf die Idee kommen, dass man den Standardunterbrechungsmechanismus gar nicht nutzt, sondern nur die Variable abfragt. Das wäre grundsätzlich möglich. Man sollte aber bedenken, dass die Standardlösung einige wartende Aufrufe  (wie wait(), sleep(), usw.) unterbricht, was bei der eigenen Lösung über die Variable nicht geht. Zusätzlich ist eine Implementierung auf Basis der Standardlösung vielleicht etwas wartbarer, weil man voraussetzen kann, dass der Standardmechanismus in der Java Community bekannt ist und zum Grundwissen der Java-Programmierer gehört.

Es gibt übrigens im Threadunterbrechungsmechanismus nichts, das vorschreibt, dass der Mechanismus nur als Ersatz für stop() und suspend() genutzt werden kann. Grundsätzlich ist man bei der Threadimplementierung frei, auch andere Anforderungen an einen Thread über diesen Mechanismus zu kommunizieren. Ein Beispiel wäre die Interpretation von interrupt() als Aufforderung zur teilweisen oder vollständigen Reinitialisierung eines Threads; man könnte den Thread beispielsweise zurücksetzen oder Teile seines Verhaltens (z.B. das Logging-Level) ändern.

Zusammenfassung

In diesem Artikel haben wir uns die Java-Mechanismen für das Anhalten und Beenden von Threads angesehen. Die ursprünglich dafür vorgesehenen Methoden Thread.stop() und Thread.suspend() sind seit JDK 1.1 deprecated, weil sie den betreffenden Thread sofort zwangsweise abbrechen und dadurch Probleme hervorrufen. Als Ersatz gibt es die Threadunterbrechungsaufforderung über Thread.interrupt().  Die Unterbrechungsanforderung ist eine Art Protokoll zwischen dem Thread, der die Unterbrechung anfordert, und dem Thread, der die Anforderung bekommt.  Der empfangende Thread wird durch die Unterbrechungsaufforderung nicht gewaltsam abgebrochen, sondern er bekommt eine freundliche Aufforderung, sich doch bitte zu beenden.  Er kann und muß dann selbst entscheiden, wie und wann er auf die Anforderung reagieren will. Wie man sinnvolle Reaktionen implementieren kann, haben wir an verschiedenen Beispielen diskutiert.
 

Literaturverweise

 
/KRE1/ Multithread Support in Java, Teil 1: Grundlagen der Multithread-Programmierung
Klaus Kreft & Angelika Langer
JavaSPEKTRUM, Januar 2004
URL: http://www.AngelikaLanger.com/Articles/EffectiveJava/12.MT-Basics/12.MT-Basics.html
/KRE2/ Multithread Support in Java, Teil 2: Details zum synchronized Schlüsselwort
Klaus Kreft & Angelika Langer
JavaSPEKTRUM, März 2004
URL: http://www.AngelikaLanger.com/Articles/EffectiveJava/13.synchronized/13.synchronized.html
/KRE3/ Multithread Support in Java, Teil 3: Erweiterungen für das Sperren von Threads im JDK 1.5
Klaus Kreft & Angelika Langer
JavaSPEKTRUM, Mai 2004
URL: http://www.AngelikaLanger.com/Articles/EffectiveJava/14.ExplicitLocks/14.ExplicitLocks.html
/KRE4/ Multithread Support in Java, Teil 4: Synchronisierung mit Hilfe von wait() und notify()/notifyAll()
Klaus Kreft & Angelika Langer
JavaSPEKTRUM, Juli 2004
URL: http://www.AngelikaLanger.com/Articles/EffectiveJava/15.WaitNotify/15.WaitNotify.html
/KRE5/ Multithread Support in Java, Teil 5: Das Nested-Monitor-Problem
Klaus Kreft & Angelika Langer
JavaSPEKTRUM, September 2004
URL: http://www.AngelikaLanger.com/Articles/EffectiveJava/16.NestedMonitorProblem/16.NestedMonitorProblem.html

 
 
 

If you are interested to hear more about this and related topics you might want to check out the following seminar:
Seminar
 
Concurrent Java - Java Multithread Programming
4 day seminar ( open enrollment and on-site)
 
  © Copyright 1995-2008 by Angelika Langer.  All Rights Reserved.    URL: < http://www.AngelikaLanger.com/Articles/EffectiveJava/17.StopThread/17.StopThread.html  last update: 26 Nov 2008