
Paulius Matulionis
Necesito una solución para detener correctamente el hilo en Java.
tengo IndexProcessor
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 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.

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.

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

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

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