Wie stoppe ich den Thread in Java richtig?

276

Ich brauche eine Lösung, um den Thread in Java richtig zu stoppen.

Ich habe eine IndexProcessorKlasse, die die Runnable-Schnittstelle implementiert:

public class IndexProcessor implements Runnable {

    private static final Logger LOGGER = LoggerFactory.getLogger(IndexProcessor.class);

    @Override
    public void run() {
        boolean run = true;
        while (run) {
            try {
                LOGGER.debug("Sleeping...");
                Thread.sleep((long) 15000);

                LOGGER.debug("Processing");
            } catch (InterruptedException e) {
                LOGGER.error("Exception", e);
                run = false;
            }
        }

    }
}

Und ich habe eine ServletContextListenerKlasse, die den Thread startet und stoppt:

public class SearchEngineContextListener implements ServletContextListener {

    private static final Logger LOGGER = LoggerFactory.getLogger(SearchEngineContextListener.class);

    private Thread thread = null;

    @Override
    public void contextInitialized(ServletContextEvent event) {
        thread = new Thread(new IndexProcessor());
        LOGGER.debug("Starting thread: " + thread);
        thread.start();
        LOGGER.debug("Background process successfully started.");
    }

    @Override
    public void contextDestroyed(ServletContextEvent event) {
        LOGGER.debug("Stopping thread: " + thread);
        if (thread != null) {
            thread.interrupt();
            LOGGER.debug("Thread successfully stopped.");
        }
    }
}

Wenn ich Tomcat herunterfahre, wird in meiner IndexProcessor-Klasse die Ausnahme angezeigt:

2012-06-09 17:04:50,671 [Thread-3] ERROR  IndexProcessor Exception
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at lt.ccl.searchengine.processor.IndexProcessor.run(IndexProcessor.java:22)
    at java.lang.Thread.run(Unknown Source)

Ich benutze JDK 1.6. Die Frage ist also:

Wie kann ich den Thread stoppen und keine Ausnahmen auslösen?

PS Ich möchte keine .stop();Methode verwenden, da diese veraltet ist.

Paulius Matulionis
quelle
1
Wenn Sie einen Thread auf halbem Weg beenden, wird immer eine Ausnahme generiert. Wenn es normales Verhalten ist, können Sie das einfach abfangen und ignorieren InterruptedException. Das denke ich, aber ich frage mich auch, wie der Standardweg ist.
nhahtdh
Ich habe Threads nicht sehr oft verwendet, daher bin ich ziemlich neu in Threads, daher weiß ich nicht, ob es normal ist, die Ausnahme zu ignorieren. Deshalb frage ich.
Paulius Matulionis
In vielen Fällen ist es normal, die Ausnahme zu ignorieren und die Methodenverarbeitung zu beenden. In meiner Antwort unten erfahren Sie, warum dies besser ist als ein flaggenbasierter Ansatz.
Matt
1
Eine übersichtliche Erklärung von B. Goetz InterruptedExceptionfinden Sie unter ibm.com/developerworks/library/j-jtp05236 .
Daniel
Die InterruptedException ist kein Problem. Ihr einziges Problem im veröffentlichten Code ist, dass Sie sie nicht als Fehler protokollieren sollten. Es gibt wirklich keinen zwingenden Grund, sie als alle zu protokollieren, außer als Debug, nur um zu demonstrieren, dass sie bei Interesse aufgetreten ist . Die ausgewählte Antwort ist unglücklich, da keine kurzen Anrufe auf Anrufe wie "Schlaf" und "Warten" gekürzt werden können.
Nathan Hughes

Antworten:

173

In der IndexProcessorKlasse müssen Sie ein Flag setzen, das den Thread darüber informiert, dass er beendet werden muss, ähnlich wie die Variablerun , die Sie gerade im Klassenbereich verwendet haben.

Wenn Sie den Thread stoppen möchten, setzen Sie dieses Flag und rufen auf join() den Thread auf und warten, bis er beendet ist.

Stellen Sie sicher, dass das Flag threadsicher ist, indem Sie eine flüchtige Variable verwenden oder Getter- und Setter-Methoden verwenden, die mit der als Flag verwendeten Variablen synchronisiert sind.

public class IndexProcessor implements Runnable {

    private static final Logger LOGGER = LoggerFactory.getLogger(IndexProcessor.class);
    private volatile boolean running = true;

    public void terminate() {
        running = false;
    }

    @Override
    public void run() {
        while (running) {
            try {
                LOGGER.debug("Sleeping...");
                Thread.sleep((long) 15000);

                LOGGER.debug("Processing");
            } catch (InterruptedException e) {
                LOGGER.error("Exception", e);
                running = false;
            }
        }

    }
}

Dann in SearchEngineContextListener:

public class SearchEngineContextListener implements ServletContextListener {

    private static final Logger LOGGER = LoggerFactory.getLogger(SearchEngineContextListener.class);

    private Thread thread = null;
    private IndexProcessor runnable = null;

    @Override
    public void contextInitialized(ServletContextEvent event) {
        runnable = new IndexProcessor();
        thread = new Thread(runnable);
        LOGGER.debug("Starting thread: " + thread);
        thread.start();
        LOGGER.debug("Background process successfully started.");
    }

    @Override
    public void contextDestroyed(ServletContextEvent event) {
        LOGGER.debug("Stopping thread: " + thread);
        if (thread != null) {
            runnable.terminate();
            thread.join();
            LOGGER.debug("Thread successfully stopped.");
        }
    }
}
DrYap
quelle
3
Ich habe genau das Gleiche getan, wie Sie die Beispiele in Ihrer Antwort angegeben haben, kurz bevor ich gesehen habe, dass Sie sie bearbeitet haben. Gute Antwort! Danke, jetzt funktioniert alles perfekt :)
Paulius Matulionis
1
Was ist, wenn die Thread-Logik komplex ist und viele Methoden anderer Klassen aufruft? Es ist nicht überall möglich, das Boolesche Flag zu überprüfen. Was ist dann zu tun?
Soteric
Sie müssten das Code-Design so ändern, dass Sie es so erstellen, dass ein Signal an Runnable dazu führt, dass der Thread beendet wird. Die meisten Anwendungen haben diese Schleife in der Ausführungsmethode, sodass das Problem normalerweise nicht auftritt.
DrYap
3
Was passiert, wenn die join () -Anweisung eine InterruptedException auslöst?
Benzaita
14
Für die Verbreitung schlechter Ratschläge abgelehnt. Der Ansatz der handgerollten Flagge bedeutet, dass die Anwendung warten muss, bis der Schlaf beendet ist, wobei eine Unterbrechung den Schlaf verkürzen würde. Es wäre einfach, dies zu ändern, um Thread # Interrupt zu verwenden.
Nathan Hughes
298

Die Verwendung Thread.interrupt()ist ein durchaus akzeptabler Weg, dies zu tun. In der Tat ist es wahrscheinlich einer Flagge vorzuziehen, wie oben vorgeschlagen. Der Grund dafür ist, dass Sie sich in einem unterbrechbaren Sperranruf befinden (wie zThread.sleep Sperranruf befinden oder mit java.nio Channel-Vorgängen), tatsächlich sofort aus diesen herausbrechen können.

Wenn Sie ein Flag verwenden, müssen Sie warten, bis der Blockierungsvorgang abgeschlossen ist, und dann können Sie Ihr Flag überprüfen. In einigen Fällen müssen Sie dies trotzdem tun, z. B. mit standard InputStream/, OutputStreamdie nicht unterbrechbar sind.

In diesem Fall wird die Unterbrechung der E / A durch einen Thread nicht unterbrochen. Sie können dies jedoch problemlos routinemäßig in Ihrem Code ausführen (und dies sollten Sie an strategischen Punkten tun, an denen Sie sicher anhalten und bereinigen können).

if (Thread.currentThread().isInterrupted()) {
  // cleanup and stop execution
  // for example a break in a loop
}

Wie ich bereits sagte, besteht der Hauptvorteil darin, Thread.interrupt()dass Sie sofort aus unterbrechbaren Anrufen ausbrechen können, was mit dem Flag-Ansatz nicht möglich ist.

Matt
quelle
32
+1 - Thread.interupt () ist definitiv der Implementierung derselben Sache mit einem Ad-hoc-Flag vorzuziehen.
Stephen C
2
Ich denke auch, dass dies der perfekte und effiziente Weg ist, dies zu tun. +1
RoboAlex
4
Der Code enthält einen kleinen Tippfehler. Thread.currentThread () enthält keine Klammern.
Vlad V
1
Tatsächlich ist es nicht vorzuziehen, ein Flag zu verwenden, da jemand anderes, der mit dem Thread in Kontakt kommt, ihn möglicherweise von einem anderen Ort aus unterbricht, wodurch er stoppt und sehr schwer zu debuggen ist. Verwenden Sie immer auch eine Flagge.
JohnyTex
In diesem speziellen Fall ist das Aufrufen interrupt()möglicherweise in Ordnung, in vielen anderen Fällen jedoch nicht (z. B. wenn eine Ressource geschlossen werden muss). Wenn jemand die innere Funktionsweise der Schleife ändert, müssen Sie daran denken, interrupt()auf die boolesche Weise zu wechseln . Ich würde von Anfang an den sicheren Weg gehen und die Flagge benutzen.
m0skit0
25

Einfache Antwort: Sie können einen Thread auf zwei Arten intern stoppen:

  • Die run-Methode trifft auf eine Return-Subroutine.
  • Die Run-Methode wird beendet und implizit zurückgegeben.

Sie können Threads auch EXTERN stoppen:

  • Anruf system.exit (dies beendet Ihren gesamten Prozess)
  • Rufen Sie das Thread-Objekt auf interrupt() Methode *
  • Überprüfen Sie, ob der Thread eine implementierte Methode hat, die so klingt, als würde sie funktionieren (wie kill()oder stop())

*: Die Erwartung ist, dass dies einen Thread stoppen soll. Was der Thread in diesem Fall tatsächlich tut, hängt jedoch ganz von dem ab, was der Entwickler beim Erstellen der Thread-Implementierung geschrieben hat.

Ein häufiges Muster, das Sie bei Implementierungen von Ausführungsmethoden sehen, ist a while(boolean){}, bei dem der Boolesche Wert normalerweise einen Namen hat isRunning, eine Mitgliedsvariable seiner Thread-Klasse ist, flüchtig ist und normalerweise von anderen Threads über eine Art Setter-Methode aufgerufen werden kann, z kill() { isRunnable=false; }. Diese Unterprogramme sind nett, weil sie es dem Thread ermöglichen, alle darin enthaltenen Ressourcen freizugeben, bevor er beendet wird.

Hamsterofdark
quelle
3
"Diese Unterprogramme sind nett, weil sie es dem Thread ermöglichen, alle darin enthaltenen Ressourcen freizugeben, bevor er beendet wird." Ich verstehe nicht. Sie können die gehaltenen Ressourcen eines Threads mit dem Status "offiziell" unterbrochen problemlos bereinigen. Überprüfen Sie es einfach mit Thread.currentThread (). IsInterrupted () oder Thread.interrupted () (je nachdem, was Ihren Anforderungen entspricht), oder fangen Sie die InterruptedException ab und bereinigen Sie sie. Wo ist das Problem?
Franz D.
Ich konnte nicht verstehen, warum die Flag-Methode funktioniert, weil ich nicht verstanden hatte, dass sie stoppt, wenn die Run-Treffer zurückkehren !!! Das war so einfach, sehr geehrter Herr, danke, dass Sie darauf hingewiesen haben, niemand hatte dies ausdrücklich getan.
Thahgr
9

Sie sollten Threads immer beenden, indem Sie ein Flag in der run()Schleife aktivieren (falls vorhanden).

Dein Thread sollte so aussehen:

public class IndexProcessor implements Runnable {

    private static final Logger LOGGER = LoggerFactory.getLogger(IndexProcessor.class);
    private volatile boolean execute;

    @Override
    public void run() {
        this.execute = true;
        while (this.execute) {
            try {
                LOGGER.debug("Sleeping...");
                Thread.sleep((long) 15000);

                LOGGER.debug("Processing");
            } catch (InterruptedException e) {
                LOGGER.error("Exception", e);
                this.execute = false;
            }
        }
    }

    public void stopExecuting() {
        this.execute = false;
    }
}

Dann können Sie den Thread beenden, indem Sie aufrufen thread.stopExecuting(). Auf diese Weise wird der Thread sauber beendet, dies dauert jedoch bis zu 15 Sekunden (aufgrund Ihres Schlafes). Sie können thread.interrupt () immer noch aufrufen, wenn es wirklich dringend ist - aber der bevorzugte Weg sollte immer darin bestehen, das Flag zu überprüfen.

Um ein Warten von 15 Sekunden zu vermeiden, können Sie den Schlaf folgendermaßen aufteilen:

        ...
        try {
            LOGGER.debug("Sleeping...");
            for (int i = 0; (i < 150) && this.execute; i++) {
                Thread.sleep((long) 100);
            }

            LOGGER.debug("Processing");
        } catch (InterruptedException e) {
        ...
Chris
quelle
2
Es ist kein Thread- es implementiert Runnable- Sie können keine ThreadMethoden darauf aufrufen , es sei denn, Sie deklarieren es als ein, Threadin welchem ​​Fall Sie nicht aufrufen könnenstopExecuting()
Don Cheadle
7

In der Regel wird ein Thread beendet, wenn er unterbrochen wird. Warum also nicht den nativen Booleschen Wert verwenden? Versuchen Sie isInterrupted ():

Thread t = new Thread(new Runnable(){
        @Override
        public void run() {
            while(!Thread.currentThread().isInterrupted()){
                // do stuff         
            }   
        }});
    t.start();

    // Sleep a second, and then interrupt
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {}
    t.interrupt();

ref- Wie kann ich einen Thread töten? ohne stop () zu verwenden;

Lovekush Vishwakarma
quelle
5

Zum Synchronisieren von Threads bevorzuge ich die Verwendung, mit CountDownLatchderen Hilfe Threads warten können, bis der ausgeführte Prozess abgeschlossen ist. In diesem Fall wird die Worker-Klasse mit einer CountDownLatchInstanz mit einer bestimmten Anzahl eingerichtet. Ein Aufruf der awaitMethode wird blockiert, bis die aktuelle Anzahl aufgrund von Aufrufen der countDownMethode Null erreicht ist oder das festgelegte Zeitlimit erreicht ist. Dieser Ansatz ermöglicht das sofortige Unterbrechen eines Threads, ohne auf die angegebene Wartezeit warten zu müssen:

public class IndexProcessor implements Runnable {

    private static final Logger LOGGER = LoggerFactory.getLogger(IndexProcessor.class);

    private final CountDownLatch countdownlatch;
    public IndexProcessor(CountDownLatch countdownlatch) {
        this.countdownlatch = countdownlatch;
    }


    public void run() {
        try {
            while (!countdownlatch.await(15000, TimeUnit.MILLISECONDS)) {
                LOGGER.debug("Processing...");
            }
        } catch (InterruptedException e) {
            LOGGER.error("Exception", e);
            run = false;
        }

    }
}

Wenn Sie die Ausführung des anderen Threads beenden möchten, führen Sie countDown für den CountDownLatchund joinden Thread für den Hauptthread aus:

public class SearchEngineContextListener implements ServletContextListener {

    private static final Logger LOGGER = LoggerFactory.getLogger(SearchEngineContextListener.class);

    private Thread thread = null;
    private IndexProcessor runnable = null;
    private CountDownLatch countdownLatch = null;

    @Override
    public void contextInitialized(ServletContextEvent event) {
        countdownLatch = new CountDownLatch(1);
        Thread thread = new Thread(new IndexProcessor(countdownLatch));
        LOGGER.debug("Starting thread: " + thread);
        thread.start();
        LOGGER.debug("Background process successfully started.");
    }

    @Override
    public void contextDestroyed(ServletContextEvent event) {
        LOGGER.debug("Stopping thread: " + thread);
        if (countdownLatch != null) 
        {
            countdownLatch.countDown();
        } 
        if (thread != null) {
            try {
                thread.join();
            } catch (InterruptedException e) {
                LOGGER.error("Exception", e);
            }
            LOGGER.debug("Thread successfully stopped.");
        } 
    }
}
Eduardo Sanchez-Ros
quelle
3

Einige ergänzende Informationen. Sowohl Flag als auch Interrupt werden im Java-Dokument vorgeschlagen.

https://docs.oracle.com/javase/8/docs/technotes/guides/concurrency/threadPrimitiveDeprecation.html

private volatile Thread blinker;

public void stop() {
    blinker = null;
}

public void run() {
    Thread thisThread = Thread.currentThread();
    while (blinker == thisThread) {
        try {
            Thread.sleep(interval);
        } catch (InterruptedException e){
        }
        repaint();
    }
}

Verwenden Sie für einen Thread, der lange wartet (z. B. auf Eingaben) Thread.interrupt

public void stop() {
     Thread moribund = waiter;
      waiter = null;
      moribund.interrupt();
 }
Feng
quelle
3
Ignorieren Sie niemals eine InterruptedException. Dies bedeutet, dass ein anderer Code Ihren Thread explizit zum Beenden auffordert. Ein Thread, der diese Anforderung ignoriert, ist ein Rogue-Thread. Der richtige Weg, um eine InterruptedException zu behandeln, besteht darin, die Schleife zu verlassen.
VGR
2

Ich habe den Interrupt nicht in Android zum Laufen gebracht, also habe ich diese Methode verwendet, funktioniert perfekt:

boolean shouldCheckUpdates = true;

private void startupCheckForUpdatesEveryFewSeconds() {
    threadCheckChat = new Thread(new CheckUpdates());
    threadCheckChat.start();
}

private class CheckUpdates implements Runnable{
    public void run() {
        while (shouldCheckUpdates){
            System.out.println("Do your thing here");
        }
    }
}

 public void stop(){
        shouldCheckUpdates = false;
 }
Mindborg
quelle
Dies wird wahrscheinlich fehlschlagen, da shouldCheckUpdates dies nicht ist volatile. Siehe docs.oracle.com/javase/specs/jls/se9/html/jls-17.html#jls-17.3 .
VGR
0

Irgendwann werde ich es 1000 Mal in meinem onDestroy () / contextDestroyed () versuchen.

      @Override
    protected void onDestroy() {
        boolean retry = true;
        int counter = 0;
        while(retry && counter<1000)
        {
            counter++;
            try{thread.setRunnung(false);
                thread.join();
                retry = false;
                thread = null; //garbage can coll
            }catch(InterruptedException e){e.printStackTrace();}
        }

    }
Ebin Joy
quelle