Come interrompere correttamente il thread in Java?


276

Ho bisogno di una soluzione per interrompere correttamente il thread in Java.

Ho una IndexProcessorclasse che implementa l'interfaccia 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;
            }
        }

    }
}

E ho una ServletContextListenerclasse che inizia e ferma il thread:

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

Ma quando chiudo Tomcat, ottengo l'eccezione nella mia 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)

Sto usando JDK 1.6. Quindi la domanda è:

Come posso interrompere il thread e non generare eccezioni?

PS Non voglio usare il .stop();metodo perché è deprecato.


1
Terminare un thread a metà genera sempre un'eccezione. Se si tratta di un comportamento normale, puoi semplicemente catturare e ignorare il InterruptedException. Questo è quello che penso, ma mi chiedo anche come sia il modo standard.
nhahtdh,

Non ho usato i thread molto spesso, quindi sono abbastanza nuovo nei thread, quindi non so se è normale comportamento ignorare l'eccezione. Ecco perché lo sto chiedendo.
Paulius Matulionis,

In molti casi è normale ignorare l'eccezione e terminare l'elaborazione del metodo. Vedi la mia risposta di seguito per capire perché questo è meglio di un approccio basato sulla bandiera.
Matt

1
Una spiegazione chiara di B. Goetz in merito InterruptedExceptionè disponibile all'indirizzo ibm.com/developerworks/library/j-jtp05236 .
Daniel,

InterruptedException non è un problema, il tuo unico problema nel codice pubblicato è che non dovresti registrarlo come errore, non c'è davvero un motivo convincente per registrarlo come tutti tranne che come debug solo per dimostrare che è successo nel caso ti interessi . la risposta selezionata è sfortunata perché non consente di tagliare le chiamate brevi a chiamate come sospensione e attesa.
Nathan Hughes,

Risposte:


173

Nella IndexProcessorclasse hai bisogno di un modo per impostare un flag che informi il thread che dovrà terminare, in modo simile alla variabile runche hai usato solo nell'ambito della classe.

Quando si desidera interrompere il thread, impostare questo flag e chiamare join()il thread e attendere che termini.

Assicurarsi che il flag sia sicuro per il thread utilizzando una variabile volatile o utilizzando i metodi getter e setter sincronizzati con la variabile utilizzata come flag.

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

    }
}

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

3
Ho fatto esattamente la stessa cosa che hai dato agli esempi nella tua risposta poco prima di vedere che l'hai modificata. Bella risposta! Grazie, ora tutto funziona perfettamente :)
Paulius Matulionis,

1
Cosa succede se la logica del thread è complessa e invoca molti metodi di altre classi? Non è possibile controllare la bandiera booleana ovunque. Cosa fare allora?
Soteric,

Dovresti cambiare il design del codice in modo da costruirlo in un modo in cui un segnale a Runnable provocherà la chiusura del thread. La maggior parte degli usi ha questo ciclo nel metodo run, quindi di solito non c'è il problema.
DrYap,

3
Cosa succede se l'istruzione join () genera un'eccezione InterruptedException?
benzaita,

14
Sottovalutato per aver diffuso cattivi consigli. l'approccio del flag ruotato a mano significa che l'applicazione deve attendere il termine del sonno, in cui l'interruzione interrompe il sonno. Sarebbe facile modificarlo per usare Thread # interrupt.
Nathan Hughes,

298

L'utilizzo Thread.interrupt()è un modo perfettamente accettabile per farlo. In effetti, è probabilmente preferibile una bandiera come suggerito sopra. Il motivo è che se stai effettuando una chiamata di blocco interrompibile (come Thread.sleepo usando le operazioni del canale java.nio), sarai effettivamente in grado di uscire da quelli immediatamente.

Se si utilizza un flag, è necessario attendere il completamento dell'operazione di blocco e quindi è possibile controllare il flag. In alcuni casi è necessario farlo comunque, ad esempio utilizzando standard InputStream/ OutputStreamche non sono interrompibili.

In tal caso, quando un thread viene interrotto, non interromperà l'IO, tuttavia, puoi facilmente farlo regolarmente nel tuo codice (e dovresti farlo in punti strategici in cui puoi fermarti e ripulire in modo sicuro)

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

Come ho detto, il vantaggio principale Thread.interrupt()è che puoi immediatamente uscire da chiamate interrompibili, cosa che non puoi fare con l'approccio flag.


32
+1 - Thread.interupt () è sicuramente preferibile per implementare la stessa cosa usando un flag ad-hoc.
Stephen C,

2
Penso anche che questo sia il modo perfetto ed efficiente per farlo. +1
RoboAlex

4
C'è un piccolo errore di battitura nel codice, Thread.currentThread () non ha la parentesi.
Vlad V

1
In realtà non è preferibile usare un flag perché qualcun altro che viene a contatto con il thread può interromperlo da qualche altra parte, causando il suo arresto ed essendo molto difficile eseguire il debug. Usa sempre anche una bandiera.
JohnyTex,

In questo caso specifico, chiamare interrupt()potrebbe essere ok, ma in molti altri casi non lo è (ad esempio se una risorsa deve essere chiusa). Se qualcuno cambia il funzionamento interno del loop, dovresti ricordare di cambiare interrupt()in modo booleano. Vorrei andare in modo sicuro fin dall'inizio e usare la bandiera.
m0skit0,

25

Risposta semplice: puoi interrompere una discussione INTERAMENTE in uno dei due modi più comuni:

  • Il metodo run esegue una subroutine di ritorno.
  • Il metodo di esecuzione termina e restituisce implicitamente.

Puoi anche interrompere i thread ESTERNI:

  • Chiama system.exit(questo uccide l'intero processo)
  • Chiama il interrupt()metodo dell'oggetto thread *
  • Verifica se il thread ha un metodo implementato che sembra funzionare (come kill()o stop())

*: L'aspettativa è che questo dovrebbe fermare un thread. Tuttavia, ciò che il thread effettivamente fa quando ciò accade dipende interamente da ciò che lo sviluppatore ha scritto quando ha creato l'implementazione del thread.

Un modello comune che vedi con le implementazioni del metodo run è a while(boolean){}, in cui il booleano è in genere un nome isRunning, è una variabile membro della sua classe thread, è volatile e in genere accessibile da altri thread con un metodo setter di sorta, ad es kill() { isRunnable=false; }. Queste subroutine sono utili perché consentono al thread di rilasciare tutte le risorse in suo possesso prima di terminare.


3
"Queste subroutine sono utili perché consentono al thread di rilasciare tutte le risorse in suo possesso prima di terminare." Non capisco. Puoi ripulire perfettamente le risorse trattenute di un thread usando lo stato interrotto "ufficiale". Basta controllarlo usando Thread.currentThread (). IsInterrupted () o Thread.interrupted () (a seconda delle necessità), oppure intercettare InterruptedException e ripulire. Dov'è il problema?
Franz D.

Non sono riuscito a capire perché il metodo flag funzioni, perché non avevo capito che si interrompe quando ritornano i colpi di corsa !!! Era così semplice, caro signore, grazie per averlo sottolineato, nessuno l'aveva fatto esplicitamente.
Thahgr,

9

Dovresti sempre terminare i thread controllando un flag nel run()loop (se presente).

Il tuo thread dovrebbe apparire così:

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

Quindi puoi terminare il thread chiamando thread.stopExecuting(). In questo modo il thread è finito pulito, ma ciò richiede fino a 15 secondi (a causa del sonno). Puoi ancora chiamare thread.interrupt () se è davvero urgente, ma il modo preferito dovrebbe sempre controllare la bandiera.

Per evitare l'attesa di 15 secondi, puoi dividere il sonno in questo modo:

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

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

2
non è un Thread- implementa Runnable- non puoi chiamarci Threadmetodi a meno che tu non lo dichiari come Threadnel qual caso non puoi chiamarestopExecuting()
Don Cheadle

7

In genere, un thread viene terminato quando viene interrotto. Quindi, perché non usare il booleano nativo? Prova 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- Come posso uccidere un thread? senza usare stop ();


5

Per sincronizzare i thread preferisco usare CountDownLatchquale aiuta i thread ad attendere fino al completamento del processo. In questo caso, la classe worker è impostata con CountDownLatchun'istanza con un determinato conteggio. Una chiamata al awaitmetodo verrà bloccata fino a quando il conteggio corrente non raggiunge lo zero a causa di invocazioni del countDownmetodo o al raggiungimento del timeout impostato. Questo approccio consente di interrompere immediatamente un thread senza dover attendere che trascorra il tempo di attesa specificato:

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

    }
}

Quando vuoi terminare l'esecuzione dell'altro thread, esegui countDown su CountDownLatche joinil thread sul thread principale:

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

3

Alcune informazioni supplementari. Sia il flag che l'interrupt sono suggeriti nel documento Java.

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

Per un thread che attende per lunghi periodi (ad es. Per input), utilizzare Thread.interrupt

public void stop() {
     Thread moribund = waiter;
      waiter = null;
      moribund.interrupt();
 }

3
Non ignorare mai un'eccezione Interrupted. Significa che qualche altro codice chiede esplicitamente al tuo thread di terminare. Un thread che ignora quella richiesta è un thread non autorizzato. Il modo corretto di gestire un InterruptedException è di uscire dal ciclo.
VGR

2

Non ho interrotto il funzionamento in Android, quindi ho usato questo metodo, funziona perfettamente:

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

È probabile che ciò non vada a buon fine, perché non dovrebbe esserlo CheckUpdates volatile. Vedi docs.oracle.com/javase/specs/jls/se9/html/jls-17.html#jls-17.3 .
VGR

0

A volte proverò 1000 volte nel mio onDestroy () / contextDestroyed ()

      @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();}
        }

    }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.