Advanced Java Services | interrupt(), isInterrupted(), interrupted() |
Wenn ein Threadobjekt angelegt wird, so wird ein Flag, das sogenannte Interruptflag auf false gesetzt. Wird nun für dieses Objekt die Methode interrupt() aufgerufen, so wird dieses Flag auf true gesetzt. Das folgende Beispiel zeigt dies.
Die folgende Klasse implemtiert Runnable und setzt eine Methode terminate() wie oben beschrieben ein. In einer Quasiendlosschleife in einem try catch wird der Interruptstatus ausgegeben. Mit try catch soll hier gezeigt werden, daß bei diesem Vorgang keine Exception geworfen wird.
public class Runnable1 implements Runnable { private boolean running = true; @Override public void run() { try { int i = 0; for(;running;) { System.out.println("runnin' " + ++i); System.out.println("interrupted = " + Thread.currentThread().isInterrupted()); // some work... } } catch(Exception ex) { System.out.println(ex); } System.out.println("end run"); } public void terminate() { running = false; } }
Die main()-Methode
/** * Runnable1 läuft quasiunendlich * main wartet 10 millisec und setzt dann die interruptaufforderung ab. * danach wartet main wieder 10 millisec und beendet dann den thread * man, daß die interruptanforderung ankommt, aber keine exception auslöst */ public static void main(String[] args) { Runnable1 ru = new Runnable1(); Thread th = new Thread(ru); System.out.println("State = " + th.getState()); System.out.println("isInterrupted = " + th.isInterrupted()); th.start(); System.out.println("State = " + th.getState()); System.out.println("isInterrupted = " + th.isInterrupted()); try { TimeUnit.MILLISECONDS.sleep(10); System.out.println("*** main interrupts " + th.getName() + " ***"); th.interrupt(); TimeUnit.MILLISECONDS.sleep(10); ru.terminate(); } catch(InterruptedException ex) { } }
Ausschnitt aus dem Ablauf
State = NEW isInterrupted = false State = RUNNABLE isInterrupted = false runnin' 1 interrupted = false runnin' 2 interrupted = false runnin' 3 ... runnin' 186 interrupted = false runnin' 187 interrupted = false *** main interrupts Thread-0 *** runnin' 188 interrupted = true runnin' 189 interrupted = true ... runnin' 335 interrupted = true runnin' 336 end main interrupted = true end run
Man erkennt hier drei wichtige Eigenschaften:
Will man einen Thread einfach beenden, wenn eine Interruptaufforderung angekommen ist, so kann man in run() auf die Methode isInterrupted() entsprechend reagieren, etwa mit einem return.
if (Thread.currentThread().isInterrupted()) return;
Vorsicht ist jedoch geboten, wenn man in run() sleep()-Phasen hat oder mit wait() oder join() arbeitet, siehe dazu weiter unten das besondere Verhalten dieser drei Methoden .
Zur Verwirrung gibt es aber neben der nichtstatischen Methode isInterrupted() noch die statische Methode interrupted() aus der Klasse Thread. Sie arbeitet zum aktuellen Thread und damit kann man auch mit dieser Methode den Status des Interruptflags abfragen. Im nächsten Beispiel werden beide Methoden verwendet.
Unser Runnable sieht nun wie folgt aus. isInterrupted() wird einmal aufgerufen, interrupted() zweimal direkt nacheinander.
public class Runnable2 implements Runnable { private boolean running = true; public void run() { int i = 0; for(; running;) { System.out.println("runnin' " + ++i); System.out.println("isInterrupted = " + Thread.currentThread().isInterrupted()); System.out.println("interrupted 1 = " + Thread.interrupted() + " *****"); System.out.println("interrupted 2 = " + Thread.interrupted() + " *****"); } System.out.println("end run"); } public void stop() { running = false; } }
main
/** * Runnable2: * wie Runnable1 aber ohne try catch * main wartet 10 millisec und setzt dann die interruptaufforderung ab. * main wartet wieder 10 millisec und beendet dann den thread */ public static void main(String[] args) { Runnable2 ru = new Runnable2(); Thread th = new Thread(ru); th.start(); try { Thread.sleep(10); System.out.println("*** main interrupts " + th.getName() + " ***"); th.interrupt(); Thread.sleep(10); ru.stop(); } catch(InterruptedException ex) { } }
Ausschnitt aus dem Ablauf. Man beachte die rot eingefärbten Zeilen!
runnin' 1
isInterrupted = false
interrupted 1 = false *****
interrupted 2 = false *****
runnin' 2
isInterrupted = false
interrupted 1 = false *****
interrupted 2 = false *****
...
...
runnin' 67
isInterrupted = false
interrupted 1 = false *****
interrupted 2 = false *****
*** main interrupts Thread-0 ***
runnin' 68
isInterrupted = true
interrupted 1 = true *****
interrupted 2 = false *****
runnin' 69
isInterrupted = false
interrupted 1 = false *****
interrupted 2 = false *****
...
...
Man sieht hier, was die statische Methode 'anrichtet':
Ist das Interruptflag auf true gesetzt so setzt ein Aufruf von interrupted() das Flag zurück auf false. Die nachfolgenden Aufrufe liefert
dann false.
Diese drei Methoden haben das folgende besondere Verhalten:
Das folgende Beispiel zeigt, daß im catch()-Zweig das Interruptflag auf false steht.
Runnable hat nun ein sleep() und ein try catch. Der Status des Flags wird vor sleep() und in catch ausgegeben.
public class Runnable3 implements Runnable { @Override public void run() { for(int i=0; i < 5; ++i) { System.out.println(Thread.currentThread().getName() + " runnin'" + i); try { boolean isInterrupted = Thread.currentThread().isInterrupted(); System.out.println("vor sleep: isInterrupted = " + isInterrupted); TimeUnit.MILLISECONDS.sleep(300); // sleep reagiert auf die interrupt-aufforderung, setzt aber das interrupt-flag zurück } catch(InterruptedException ex) { System.out.println(ex); boolean isInterrupted = Thread.currentThread().isInterrupted(); System.out.println("in catch: isInterrupted = " + isInterrupted); // ändert den status nicht System.out.println("in catch: interrupted() = " + Thread.interrupted() ); // ändert den status auf false, falls er true war. } } } }
main
private static void interrupt3() { Thread th = new Thread(new Runnable3()); th.start(); try { Thread.sleep(300); System.out.println("*** + main interrupts " + th.getName() + " ***"); th.interrupt(); } catch(InterruptedException ex) { } }
In den allermeisten Fällen sieht der Ablauf folgendermaßen auf
Thread-0 runnin'0 vor sleep: isInterrupted = false Thread-0 runnin'1 vor sleep: isInterrupted = false *** main interrupts Thread-0 *** end main java.lang.InterruptedException: sleep interrupted in catch: isInterrupted = false in catch: interrupted() = false Thread-0 runnin'2 vor sleep: isInterrupted = false Thread-0 runnin'3 vor sleep: isInterrupted = false Thread-0 runnin'4 vor sleep: isInterrupted = false
Mit einiger Geduld kann er aber auch mal so aussehen. Hier sieht man, daß das Interruptflag tatsächlich zuerst auf true gesetzt wird, im catch()-Zweig dann aber wieder false ist.
Thread-0 runnin'0 vor sleep: isInterrupted = false *** main interrupts Thread-0 *** end main Thread-0 runnin'1 vor sleep: isInterrupted = true *** java.lang.InterruptedException: sleep interrupted in catch: isInterrupted = false in catch: interrupted() = false Thread-0 runnin'2 vor sleep: isInterrupted = false Thread-0 runnin'3 vor sleep: isInterrupted = false Thread-0 runnin'4 vor sleep: isInterrupted = false
Will man bei einer InterruptedException auf jeden Fall beenden, dann reicht ein return im catch()-Zweig.
Will man aber VOR catch durch die Abfrage von isInterrupted() reagieren so kann man sich leicht selbst in den Fuß schießen. Das folgende Runnable läuft nämlich endlos.
public class Runnable4 implements Runnable { @Override public void run() { System.out.println("vor while"); while(!Thread.currentThread().isInterrupted()) { System.out.println("runnin'"); try { Thread.sleep(20); } catch(InterruptedException ex) { System.out.println(ex); } } System.out.println("end run"); } }
In der obigen Situation trifft ein Interrupt praktisch immer während der sleep()-Phase ein und somit ist das Flag im catch()-Zweig bereits wieder auf false gesetzt worden und die Schleife eine Endlosschleife. Abhilfe schafft hier ein erneuter Aufruf von interrupt() in catch(). Dieser Aufruf trifft den Thread mit Sicherheit nicht in einer sleep()-Phase, das Flag wird also auf true gesetzt und bleibt auf true, da keine Exception ausgelöst wird und so wird die Schleife beendet.
public class Runnable4 implements Runnable
{
@Override
public void run()
{
System.out.println("vor while");
while(!Thread.currentThread().isInterrupted())
{
System.out.println("runnin'");
try
{
Thread.sleep(20);
}
catch(InterruptedException ex)
{
Thread.currentThread().interrupt();
System.out.println(ex);
}
}
System.out.println("end run");
}
}
Ein Thread muß keineswegs beendet sein, wenn der Mainthread endet. Threads können länger laufen als der Mainthread. Ein Programm ist erst dann zu Ende wenn auch der letzte von main aus gestartete Thread zum Ende gekommen ist. Man kann einen Thread aber so konfigurieren, daß er bei Programmende automatisch beendet wird. Dazu erzeugt man einen sog. Dämonthread, indem man vor dem Start des Threads die Methode setDaemon(boolean on) mit true aufruft. Die JVM beendet dann diesen Thread automatisch, wenn der letzte (Nichtdämon-) Thread sich beendet hat.
Das folgende Programm startet zwei Threads, einen Dämon und einen normalen Thread. Der normale Thread läuft nach Beendigung von main noch eine Weile. Der Dämonthread enthält eine Endlosschleife, wird aber trotzdem nach dem Ende des 'Nichtdämons' beendet.
public class Daemon extends Thread { public Daemon() { this.setDaemon(true); } @Override public void run() { for(int i = 0; i>=0 ; i++) { System.out.println("damon"); try { sleep(500); } catch(InterruptedException ex) { System.out.println(ex); } } System.out.println("end damon"); } }
public class NoDaemon extends Thread { @Override public void run() { for(int i = 0; i < 5; i++) { System.out.println("no damon"); try { sleep(1000); } catch(InterruptedException ex) { ex.printStackTrace(); } } System.out.println("end nodamon"); } }
public class Main { public static void main(String[] args) { Daemon daemon = new Daemon(); NoDaemon noDaemon = new NoDaemon(); daemon.start(); noDaemon.start(); System.out.println("end main"); } }
Das Programm macht in etwa die folgende Ausgabe. Man sieht sehr deutlich, daß der Dämon erst dann beendet wird, wenn der (letzte) von main aus gestartete Thread endet (nach "NoDaemon end" erfolgt keine weitere Ausgabe).
damon no damon end main damon no damon damon damon no damon damon damon no damon damon damon no damon damon damon end nodamon