Cómo detener correctamente el hilo en Java?

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

Tengo la clase IndexProcessor 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 la clase ServletContextListener 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 arrojar ninguna excepción?

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

En la clase IndexProcessor necesita una forma de establecer un indicador que informe al hilo que tendrá que terminar, de forma similar a la run variable que ha utilizado solo en el ámbito de clase.

Cuando desee detener el hilo, configure este indicador y llame a join() en el hilo y espere a que termine.

Asegúrese de que la bandera es segura para subprocesos mediante el uso de una variable volátil o mediante el uso de métodos getter y setter que se sincronizan 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."); } } } 

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

Si usa un indicador, debe esperar a que finalice la operación de locking y luego puede verificar su indicador. En algunos casos, debe hacer esto de todos modos, como utilizar InputStream / OutputStream estándar que no son interruptables.

En ese caso, cuando se interrumpe un hilo, no interrumpe el IO, sin embargo, puede hacer esto de manera rutinaria en su código (y debe hacerlo en puntos estratégicos donde puede parar y limpiar de forma 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 indicador.

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

  • El método de ejecución golpea una subrutina de retorno.
  • El método Run termina y regresa implícitamente.

También puede detener hilos EXTERNALMENTE:

  • Llamar a system.exit (esto mata todo el proceso)
  • Llamar al método interrupt() del objeto thread *
  • 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 detiene un hilo. Sin embargo, lo que el hilo realmente hace cuando esto sucede depende por completo de lo que escribió el desarrollador cuando creó la implementación del hilo.

Un patrón común que se ve con implementaciones de métodos de ejecución es while(boolean){} , donde boolean es típicamente algo llamado isRunning , es una variable miembro de su clase de subprocesos, es volátil y normalmente accesible por otros subprocesos mediante un método setter de tipo, por ejemplo, kill() { isRunnable=false; } kill() { isRunnable=false; } . Estas subrutinas son agradables porque permiten que el hilo libere cualquier recurso que tenga antes de terminar.

Siempre debe finalizar los hilos al marcar un indicador en el ciclo run() (si corresponde).

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

Luego puede finalizar el hilo llamando a thread.stopExecuting() . De esta forma, el hilo se 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 debería ser verificar el indicador.

Para evitar esperar 15 segundos, puedes dividir el sueño así:

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

Para sincronizar hilos, prefiero utilizar CountDownLatch que ayuda a los hilos a esperar hasta que se complete el proceso. En este caso, la clase de trabajador está configurada con una instancia CountDownLatch con un recuento dado. Un método de llamada a la await se bloqueará hasta que el recuento actual llegue a cero debido a las invocaciones del método countDown o cuando se scope el tiempo de espera establecido. Este enfoque permite interrumpir un hilo al instante 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 hilo, ejecute countDown en CountDownLatch y join al 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."); } } } 

Si estamos usando JDK 1.0, podemos llamar al método en desuso de Thread stop () para terminarlo. Usar stop () es increíblemente peligroso, ya que matará a su hilo incluso si está en medio de algo importante. No hay forma de protegerse, así que si ve código que usa stop (), debe fruncir el ceño.

¿Cómo cerramos un hilo limpiamente?

En Java, iniciar un hilo es fácil, pero cerrarlos requiere mucha atención y esfuerzos.

Así es como está diseñado en Java. Hay una bandera llamada indicador de estado de interrupción en cada hilo de java que podemos establecer desde el exterior, es decir, padre o hilo principal. Y el hilo puede verificarlo ocasionalmente y detiene su ejecución. Voluntariamente..!! Aquí es cómo:

 Thread loop = new Thread(new Runnable() { @Override public void run() { while (true) { if (Thread.interrupted()) { break; } // Continue to do nothing } }}); loop.start(); loop.interrupt(); 

fuente: Cómo detener el hilo en Java | MultiThreading

Alguna información suplementaria. 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 hilo que espera durante largos períodos (por ejemplo, para la entrada), use Thread.interrupt

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

No obtuve la interrupción para trabajar en Android, así que usé este método, funciona perfectamente:

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