¿Cómo detener correctamente el hilo en Java?

13 minutos de lectura

¿Como detener correctamente el hilo en Java
Paulius Matulionis

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

tengo IndexProcessorclase 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 yo tengo ServletContextListener 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 apago Tomcat, obtengo 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 lanzar ninguna excepción?

PD no quiero usar .stop(); método porque está en desuso.

  • Terminar un subproceso a la mitad siempre generará una excepción. Si se trata de un comportamiento normal, entonces puede atrapar e ignorar el InterruptedException. Esto es lo que pienso, pero también me pregunto cómo es la forma estándar.

    – nhahtdh

    9 de junio de 2012 a las 14:18


  • No he estado usando subprocesos con mucha frecuencia, por lo que soy bastante nuevo en los subprocesos, por lo que no sé si es un comportamiento normal ignorar la excepción. Por eso pregunto.

    – Paulius Matulionis

    9 de junio de 2012 a las 14:23


  • En muchos casos, es un comportamiento normal ignorar la excepción y terminar el procesamiento del método. Vea mi respuesta a continuación para saber por qué esto es mejor que un enfoque basado en banderas.

    – Mate

    9 de junio de 2012 a las 16:29

  • Una clara explicación de B. Goetz sobre InterruptedException se puede encontrar en ibm.com/developerworks/library/j-jtp05236.

    – Daniel

    30 de diciembre de 2014 a las 19:33

  • la InterruptedException no es un problema, su único problema en el código publicado es que no debe registrarlo como un error, realmente no hay una razón convincente para registrarlo como todo excepto como depuración solo para demostrar que sucedió en caso de que esté interesado . la respuesta seleccionada es desafortunada porque no permite acortar llamadas a llamadas como dormir y esperar.

    – Nathan Hughes

    13 de febrero de 2015 a las 20:57


1646955791 494 ¿Como detener correctamente el hilo en Java
Mate

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

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

En ese caso, cuando se interrumpe un subproceso, no interrumpirá el 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 limpiar de manera segura)

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

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

  • +1 – Thread.interupt() es definitivamente preferible a implementar lo mismo usando una bandera ad-hoc.

    – Esteban C.

    6 de abril de 2013 a las 4:57

  • También creo que esta es la manera perfecta y eficiente de hacerlo. +1

    – RoboAlex

    10 de abril de 2013 a las 15:27

  • Hay un pequeño error tipográfico en el código, Thread.currentThread() no tiene paréntesis.

    – Vlad V.

    12 de diciembre de 2013 a las 0:13

  • En realidad, no es preferible usar una bandera porque alguien más que entre en contacto con el hilo puede interrumpirlo desde otro lugar, causando que se detenga y sea muy difícil de depurar. Siempre use una bandera también.

    – JohnyTex

    18 de enero de 2016 a las 8:10

  • En este caso específico llamando interrupt() podría estar bien, pero en muchos otros casos no lo está (por ejemplo, si es necesario cerrar un recurso). Si alguien cambia el funcionamiento interno del bucle, debe recordar cambiar interrupt() a la manera booleana. Iría por el camino seguro desde el principio y usaría la bandera.

    – m0skit0

    23 de septiembre de 2016 a las 9:00


1646955792 42 ¿Como detener correctamente el hilo en Java
DrYap

En el IndexProcessor clase, necesita una forma de establecer una bandera que informe al subproceso que deberá terminar, similar a la variable run que ha utilizado solo en el ámbito de la clase.

Cuando desee detener el hilo, establezca esta bandera y llame join() en el hilo y esperar a que termine.

Asegúrese de que el indicador sea seguro para subprocesos mediante el uso de una variable volátil o mediante el uso de 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.");
        }
    }
}

  • Hice exactamente lo mismo que diste en los ejemplos en tu respuesta justo antes de ver que lo editaste. ¡Gran respuesta! Gracias, ahora todo funciona perfectamente 🙂

    – Paulius Matulionis

    9 de junio de 2012 a las 14:39

  • ¿Qué pasa si la lógica del subproceso es compleja e invoca muchos métodos de otras clases? No es posible verificar la bandera booleana en todas partes. ¿Qué hacer entonces?

    – Sotérico

    9 de junio de 2012 a las 14:53

  • Tendría que cambiar el diseño del código para construirlo de manera que una señal al Runnable haga que el subproceso se cierre. La mayoría de los usos tienen este ciclo en el método de ejecución, por lo que generalmente no existe el problema.

    – DrYap

    9 de junio de 2012 a las 14:57


  • ¿Qué sucede si la instrucción join() arroja una InterruptedException?

    – benzita

    19 de noviembre de 2014 a las 9:53

  • Votado negativo por difundir malos consejos. el enfoque de la bandera enrollada a mano significa que la aplicación tiene que esperar a que termine la suspensión, donde la interrupción acortaría la suspensión. Sería fácil modificar esto para usar Thread#interrupt.

    – Nathan Hughes

    9 oct 2015 a las 12:28

¿Como detener correctamente el hilo en Java
hámster de la oscuridad

Respuesta simple: puede detener un hilo INTERNAMENTE de una de las dos formas comunes:

  • El método de ejecución llega a una subrutina de retorno.
  • El método de ejecución finaliza y regresa implícitamente.

También puede detener hilos EXTERNAMENTE:

  • Llamada system.exit (esto mata todo su proceso)
  • Llame al objeto del hilo interrupt() método *
  • Vea si el hilo tiene un método implementado que parece que funcionaría (como kill() o stop())

*: La expectativa es que se supone que esto detenga un hilo. Sin embargo, lo que realmente hace el subproceso cuando esto sucede depende totalmente de lo que escribió el desarrollador cuando creó la implementación del subproceso.

Un patrón común que ve con las implementaciones del método de ejecución es un while(boolean){}donde el valor booleano suele ser algo llamado isRunninges una variable miembro de su clase de subproceso, es volátil y, por lo general, es accesible para otros subprocesos mediante una especie de método de establecimiento, por ejemplo kill() { isRunnable=false; }. Estas subrutinas son buenas porque permiten que el subproceso libere cualquier recurso que tenga antes de terminar.

  • “Estas subrutinas son buenas porque permiten que el subproceso libere cualquier recurso que tenga antes de terminar”. No entiendo. Puede limpiar perfectamente los recursos retenidos de un subproceso utilizando el estado interrumpido “oficial”. Simplemente verifíquelo usando Thread.currentThread().isInterrupted() o Thread.interrupted() (lo que se ajuste a sus necesidades), o capture la InterruptedException y limpie. ¿Dónde está el problema?

    – Francisco D.

    19 de agosto de 2018 a las 19:29

  • ¡No pude entender por qué funciona el método de la bandera, porque no había entendido que se detiene cuando la ejecución golpea regresar! Esto fue tan simple, estimado señor, gracias por señalar esto, nadie lo había hecho explícitamente.

    – thahgr

    2 de enero de 2019 a las 14:51

Siempre debe finalizar los hilos marcando una bandera en el run() bucle (si lo hay).

Tu hilo debería verse así:

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

Entonces puedes terminar el hilo llamando thread.stopExecuting(). De esa manera, el hilo termina limpio, pero esto toma hasta 15 segundos (debido a su sueño). Todavía puede llamar a thread.interrupt() si es realmente urgente, pero la forma preferida siempre debe ser verificar la bandera.

Para evitar esperar 15 segundos, puede dividir el sueño de esta manera:

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

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

Por lo general, un subproceso finaliza cuando se interrumpe. Entonces, ¿por qué no usar el booleano nativo? Prueba 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- ¿Cómo puedo matar un hilo? sin usar stop();

  • El hilo interrumpido NO ES terminado, simplemente se interrumpe. Si el hilo se interrumpe mientras está en sleep()entonces lanza InterruptedException que su hilo debería atrapar y posiblemente terminar solo o tal vez simplemente continuar con el procesamiento adicional. isInterrutped sera true SOLAMENTE si el hilo no estaba en sleep o wait (en realidad se estaba ejecutando) y esto puede darle una pista de que el hilo se interrumpió. En otras palabras: si pones sleep() en tus // do stuff línea, entonces t.interrupt() no lo terminará (bueno, para el 99,9% no lo hará).

    – Cromax

    29 de junio de 2020 a las 22:40

  • La primera oración de la respuesta debe decir “Por lo general, el objetivo de la interrupción es terminar un hilo”. Para aclarar lo que dijo @Cromax, si esto while condición va a ser la única forma de detener el hilo, entonces cualquier catch (InterruptedException) el bloque necesita llamar Thread.currentThread().interrupt(); con el fin de mantener el estado interrumpido para el while condición. Pero dependiendo del trabajo, podría ser seguro usar esos catch bloques a break; fuera del circuito temprano, o incluso seguro para agregar más .isInterrupted() controles en todo el cuerpo del bucle.

    – AndrewF

    26 de febrero de 2021 a las 6:10


Para sincronizar hilos prefiero usar CountDownLatch lo que ayuda a los subprocesos a esperar hasta que se complete el proceso que se está realizando. En este caso, la clase obrera se configura con una CountDownLatch instancia con un conteo dado. una llamada a await El método se bloqueará hasta que el conteo actual llegue a cero debido a las invocaciones del countDown se alcanza el método o se alcanza el tiempo de espera establecido. Este enfoque permite interrumpir un hilo instantáneamente sin tener que esperar a que transcurra el tiempo de espera especificado:

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

    }
}

Cuando desee finalizar la ejecución del otro subproceso, ejecute countDown en el CountDownLatch y join el hilo al hilo principal:

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

  • El hilo interrumpido NO ES terminado, simplemente se interrumpe. Si el hilo se interrumpe mientras está en sleep()entonces lanza InterruptedException que su hilo debería atrapar y posiblemente terminar solo o tal vez simplemente continuar con el procesamiento adicional. isInterrutped sera true SOLAMENTE si el hilo no estaba en sleep o wait (en realidad se estaba ejecutando) y esto puede darle una pista de que el hilo se interrumpió. En otras palabras: si pones sleep() en tus // do stuff línea, entonces t.interrupt() no lo terminará (bueno, para el 99,9% no lo hará).

    – Cromax

    29 de junio de 2020 a las 22:40

  • La primera oración de la respuesta debe decir “Por lo general, el objetivo de la interrupción es terminar un hilo”. Para aclarar lo que dijo @Cromax, si esto while condición va a ser la única forma de detener el hilo, entonces cualquier catch (InterruptedException) el bloque necesita llamar Thread.currentThread().interrupt(); con el fin de mantener el estado interrumpido para el while condición. Pero dependiendo del trabajo, podría ser seguro usar esos catch bloques a break; fuera del circuito temprano, o incluso seguro para agregar más .isInterrupted() controles en todo el cuerpo del bucle.

    – AndrewF

    26 de febrero de 2021 a las 6:10


1646955793 708 ¿Como detener correctamente el hilo en Java
feng

Algunos datos complementarios. Tanto el indicador como la interrupción se sugieren en el documento de 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();
    }
}

Para un subproceso que espera durante largos períodos (por ejemplo, para la entrada), utilice Thread.interrupt

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

  • Nunca ignore una excepción interrumpida. Significa que algún otro código le pide explícitamente a su hilo que termine. Un subproceso que ignora esa solicitud es un subproceso deshonesto. La forma correcta de manejar una InterruptedException es salir del bucle.

    – VGR

    21 de marzo de 2018 a las 19:54

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad