317 votes

Comment arrêter correctement le Thread en Java ?

J'ai besoin d'une solution pour arrêter correctement le fil de discussion en Java.

J'ai IndexProcessor classe qui implémente l'interface Runnable :

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;
            }
        }

    }
}

Et j'ai ServletContextListener qui démarre et arrête le fil :

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.");
        }
    }
}

Mais quand je ferme tomcat, j'obtiens l'exception dans ma classe IndexProcessor :

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)

J'utilise le JDK 1.6. La question est donc la suivante :

Comment puis-je arrêter le fil et ne pas lancer d'exceptions ?

P.S. Je ne veux pas utiliser .stop(); car elle est obsolète.

1 votes

L'interruption d'un fil à mi-chemin générera toujours une exception. S'il s'agit d'un comportement normal, vous pouvez simplement attraper et ignorer l'exception. InterruptedException . C'est ce que je pense, mais je me demande aussi comment est la méthode standard.

0 votes

Je n'ai pas utilisé les fils de discussion très souvent et je suis donc assez novice en la matière. Je ne sais donc pas s'il est normal d'ignorer l'exception. C'est pourquoi je pose la question.

0 votes

Dans de nombreux cas, il est normal d'ignorer l'exception et de mettre fin au traitement de la méthode. Voir ma réponse ci-dessous pour savoir pourquoi cela est mieux qu'une approche basée sur les drapeaux.

339voto

Matt Points 5441

Utilisation de Thread.interrupt() est un moyen parfaitement acceptable de le faire. En fait, c'est probablement préférable à un drapeau comme suggéré ci-dessus. La raison est que si vous êtes dans un appel bloquant interrompable (tel que Thread.sleep ou en utilisant les opérations du canal java.nio), vous serez en fait en mesure de vous en affranchir immédiatement.

Si vous utilisez un drapeau, vous devez attendre que l'opération de blocage se termine, puis vous pouvez vérifier votre drapeau. Dans certains cas, vous devez le faire de toute façon, comme dans le cas de l'utilisation de l'option standard InputStream / OutputStream qui ne sont pas interruptibles.

Dans ce cas, lorsqu'un thread est interrompu, il n'interrompra pas l'IO, cependant, vous pouvez facilement faire cela de façon routinière dans votre code (et vous devriez le faire à des points stratégiques où vous pouvez vous arrêter et nettoyer en toute sécurité)

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

Comme je l'ai dit, le principal avantage de Thread.interrupt() c'est que vous pouvez immédiatement sortir des appels interrompus, ce que vous ne pouvez pas faire avec l'approche par drapeau.

37 votes

+1 - Thread.interupt() est définitivement préférable à la mise en œuvre de la même chose en utilisant un drapeau ad-hoc.

2 votes

Je pense également que c'est la manière parfaite et efficace de le faire. +1

4 votes

Il y a une petite coquille dans le code, Thread.currentThread() n'a pas les parenthèses.

191voto

DrYap Points 2880

Dans le IndexProcessor vous avez besoin d'un moyen de définir un drapeau qui informe le thread qu'il doit se terminer, de manière similaire à la variable run que vous avez utilisé uniquement dans la portée de la classe.

Lorsque vous souhaitez arrêter le fil, vous mettez ce drapeau et appelez join() sur le fil et attendez qu'il se termine.

Veillez à ce que l'indicateur soit sûr pour les threads en utilisant une variable volatile ou en utilisant des méthodes getter et setter qui sont synchronisées avec la variable utilisée comme indicateur.

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;
            }
        }

    }
}

Ensuite, dans 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.");
        }
    }
}

4 votes

J'ai fait exactement la même chose car vous avez donné les exemples dans votre réponse juste avant de regarder que vous l'avez modifiée. Excellente réponse ! Merci, maintenant tout fonctionne parfaitement :)

1 votes

Que se passe-t-il si la logique du fil est complexe et invoque de nombreuses méthodes d'autres classes ? Il n'est pas possible de vérifier le drapeau booléen partout. Que faire alors ?

0 votes

Il faudrait modifier la conception du code de manière à le construire de telle sorte qu'un signal envoyé au Runnable entraîne la sortie du thread. Dans la plupart des cas, cette boucle se trouve dans la méthode d'exécution, et le problème ne se pose donc généralement pas.

32voto

hamsterofdark Points 301

La réponse est simple : Vous pouvez arrêter un fil de manière INTERNE de l'une des deux manières habituelles :

  • La méthode run frappe une sous-routine de retour.
  • La méthode d'exécution se termine, et revient implicitement.

Vous pouvez également arrêter les fils de manière EXTERNE :

  • Appelez system.exit (cela détruit l'ensemble du processus)
  • Appeler l'objet thread interrupt() méthode *
  • Regardez si le fil de discussion a une méthode implémentée qui semble fonctionner (comme kill() o stop() )

* : On s'attend à ce que cela arrête un thread. Cependant, ce que le thread fait réellement lorsque cela se produit dépend entièrement de ce que le développeur a écrit lorsqu'il a créé l'implémentation du thread.

Un modèle commun que vous voyez avec les implémentations de méthodes d'exécution est un while(boolean){} où le booléen est typiquement quelque chose appelé isRunning il s'agit d'une variable membre de sa classe de thread, elle est volatile et généralement accessible par d'autres threads par une sorte de méthode setter, par ex. kill() { isRunnable=false; } . Ces sous-routines sont intéressantes car elles permettent au thread de libérer toutes les ressources qu'il détient avant de se terminer.

4 votes

"Ces sous-routines sont intéressantes car elles permettent au thread de libérer les ressources qu'il détient avant de se terminer." Je ne comprends pas. Vous pouvez parfaitement nettoyer les ressources détenues par un thread en utilisant le statut "officiel" d'interruption. Il suffit de le vérifier en utilisant Thread.currentThread().isInterrupted() ou Thread.interrupted() (selon ce qui convient le mieux), ou d'attraper l'InterruptedException, et de nettoyer. Où se situe le problème ?

0 votes

Je n'étais pas capable de comprendre pourquoi la méthode du drapeau fonctionne, parce que je n'avais pas compris qu'elle s'arrête lorsque l'exécution atteint le retour ! !! C'était si simple, cher monsieur merci de l'avoir souligné, personne ne l'avait fait explicitement.

10voto

Chris Points 2147

Vous devriez toujours terminer les threads en vérifiant un drapeau dans le fichier run() boucle (le cas échéant).

Votre fil devrait ressembler à ceci :

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;
    }
}

Ensuite, vous pouvez terminer le fil en appelant thread.stopExecuting() . De cette façon, le thread est terminé proprement, mais cela prend jusqu'à 15 secondes (en raison de votre sommeil). Vous pouvez toujours appeler thread.interrupt() si c'est vraiment urgent - mais la méthode préférée devrait toujours être de vérifier le drapeau.

Pour éviter d'attendre 15 secondes, vous pouvez diviser le sommeil comme ceci :

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

            LOGGER.debug("Processing");
        } catch (InterruptedException e) {
        ...

2 votes

Ce n'est pas un Thread -- il met en œuvre Runnable -- vous ne pouvez pas appeler Thread à moins que vous ne le déclariez en tant que Thread dans ce cas, vous ne pouvez pas appeler stopExecuting()

8voto

Love Points 1

En général, un thread est terminé lorsqu'il est interrompu. Alors, pourquoi ne pas utiliser le booléen natif ? Essayez 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- Comment puis-je tuer un thread sans utiliser stop() ;

0 votes

Le fil interrompu N'EST PAS terminé, c'est juste interrompu. Si le thread est interrompu alors qu'il est dans sleep() puis il lance InterruptedException que votre fil de discussion devrait attraper et éventuellement terminer lui-même ou simplement poursuivre le traitement. isInterrutped sera true UNIQUEMENT si le fil n'était pas dans sleep o wait (il était en cours d'exécution) et cela peut vous donner un indice, que le fil a été interrompu. En d'autres termes, si vous mettez sleep() dans votre // do stuff la ligne, puis t.interrupt() n'y mettra pas fin (enfin, pour 99,9% d'entre eux, ce ne sera pas le cas).

0 votes

La première phrase de la réponse devrait dire "Typiquement, le but de l'interruption est de terminer un fil de discussion." Pour clarifier ce que @Cromax a dit, si cela while sera le seul moyen d'arrêter le fil, alors toute condition catch (InterruptedException) doit appeler Thread.currentThread().interrupt(); afin de maintenir l'état interrompu de l'appareil. while condition. Mais en fonction du travail, il peut être sûr d'utiliser ces catch blocs pour break; de la boucle plus tôt, ou même de la sécurité pour ajouter plus de .isInterrupted() des contrôles dans tout le corps de la boucle.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X