¿Cómo detener correctamente el hilo en Java?

Resuelto Paulius Matulionis asked hace 12 años • 9 respuestas

Necesito una solución para detener correctamente el hilo en Java.

Tengo IndexProcessoruna clase que implementa la interfaz 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;
            }
        }

    }
}

Y tengo ServletContextListeneruna clase que inicia y detiene el hilo:

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

Pero cuando cierro Tomcat, aparece la excepción en mi clase 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)

Estoy usando JDK 1.6. Entonces la pregunta es:

¿Cómo puedo detener el hilo y no generar ninguna excepción?

PD: No quiero utilizar .stop();el método porque está en desuso.

Paulius Matulionis avatar Jun 09 '12 21:06 Paulius Matulionis
Aceptado

Usar Thread.interrupt()es una forma perfectamente aceptable de hacer esto. De hecho, probablemente sea preferible a una bandera como se sugirió anteriormente. La razón es que si estás en una llamada de bloqueo interrumpible (como Thread.sleepo usando operaciones del canal java.nio), podrás salir de ellas de inmediato.

Si usa una bandera, debe esperar a que finalice la operación de bloqueo y luego podrá verificar su bandera. En algunos casos, debe hacer esto de todos modos, como por ejemplo usando InputStream/ estándar OutputStreamque no son interrumpibles.

En ese caso, cuando se interrumpe un subproceso, no interrumpirá la IO; sin embargo, puede hacerlo fácilmente de forma rutinaria en su código (y debe hacerlo en puntos estratégicos donde pueda detenerse y limpiarse de manera segura).

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

Como dije, la principal ventaja Thread.interrupt()es que puedes salir inmediatamente de las llamadas interrumpibles, lo que no puedes hacer con el enfoque de bandera.

Matt avatar Jun 09 '2012 16:06 Matt

En la IndexProcessorclase necesita una forma de configurar un indicador que informe al hilo que deberá terminar, similar a la variable runque utilizó solo en el ámbito de la clase.

Cuando desee detener el hilo, configure esta bandera, llame join()al hilo y espere a que termine.

Asegúrese de que el indicador sea seguro para subprocesos utilizando una variable volátil o métodos getter y setter que estén sincronizados con la variable que se utiliza como indicador.

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

    }
}

Luego en 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 avatar Jun 09 '2012 14:06 DrYap