¿Cómo abortar un hilo de una manera rápida y limpia en Java?

Aquí está mi problema: tengo un diálogo con algunos parámetros que el usuario puede cambiar (a través de una ruleta, por ejemplo). Cada vez que se cambia uno de estos parámetros, inicio un hilo para actualizar una vista 3D de acuerdo con el nuevo valor del parámetro. Si el usuario cambia otro valor (o el mismo valor nuevamente haciendo clic muchas veces en la flecha giratoria) mientras el primer hilo está funcionando, me gustaría abortar el primer hilo (y la actualización de la vista 3D) y lanzar uno nuevo con el último valor de parámetro

¿Cómo puedo hacer algo así?

PD: No hay ningún bucle en el método run() de mi hilo, por lo que no es una opción verificar: una secuencia que actualiza la vista 3D básicamente solo llama a un único método que es muy largo de ejecutar. No puedo agregar ningún indicador en este método que pida abortar ya que no tengo acceso a su código.

Intente interrumpir () como algunos han dicho para ver si hace alguna diferencia en su hilo. Si no, intente destruir o cerrar un recurso que detendrá el hilo. Eso tiene una posibilidad de ser un poco mejor que tratar de lanzar Thread.stop () a él.

Si el rendimiento es tolerable, puede ver cada actualización 3D como un evento discreto no interrumpible y simplemente dejar que se ejecute hasta la conclusión, verificando luego si hay una nueva actualización más reciente para realizar. Esto podría hacer que la GUI sea un poco agitada para los usuarios, ya que podrían hacer cinco cambios, luego ver los resultados gráficos de cómo eran las cosas hace cinco cambios, y luego ver el resultado de su último cambio. Pero dependiendo de cuánto dura este proceso, podría ser tolerable y evitaría tener que matar el hilo. El diseño puede verse así:

 boolean stopFlag = false; Object[] latestArgs = null; public void run() { while (!stopFlag) { if (latestArgs != null) { Object[] args = latestArgs; latestArgs = null; perform3dUpdate(args); } else { Thread.sleep(500); } } } public void endThread() { stopFlag = true; } public void updateSettings(Object[] args) { latestArgs = args; } 

El hilo que está actualizando la vista 3D debería revisar periódicamente alguna bandera (use un volatile boolean ) para ver si debería terminar. Cuando desee abortar el hilo, simplemente configure el indicador. Cuando el hilo vuelva a verificar el indicador, simplemente debería salir del bucle que está utilizando para actualizar la vista y regresar de su método de run .

Si realmente no puede acceder al código que el hilo está ejecutando para que marque un indicador, entonces no hay una forma segura de detener el hilo. ¿Este subproceso termina alguna vez normalmente antes de que se complete su aplicación? Si es así, ¿qué causa que se detenga?

Si se ejecuta durante un período de tiempo prolongado y simplemente debe finalizarlo, puede considerar utilizar el método Thread.stop() desuso. Sin embargo, fue desaprobado por una buena razón. Si ese subproceso se detiene mientras está en medio de una operación que deja algo en un estado incoherente o algún recurso no se limpia correctamente, entonces podría estar en problemas. Aquí hay una nota de la documentación :

Este método es inherentemente inseguro. Detener un hilo con Thread.stop hace que desbloquee todos los monitores que ha bloqueado (como consecuencia natural de la excepción ThreadDeath no verificada que se propaga por la stack). Si alguno de los objetos previamente protegidos por estos monitores se encontraba en un estado incoherente, los objetos dañados se vuelven visibles para otros hilos, lo que puede generar un comportamiento arbitrario. Muchos usos de la parada deberían reemplazarse por un código que simplemente modifique alguna variable para indicar que la secuencia de destino debería dejar de ejecutarse. El hilo de destino debe verificar esta variable regularmente, y regresar de su método de ejecución de manera ordenada si la variable indica que debe dejar de ejecutarse. Si el subproceso objective espera períodos largos (en una variable de condición, por ejemplo), el método de interrupción se debe utilizar para interrumpir la espera. Para obtener más información, consulte ¿Por qué Thread.stop, Thread.suspend y Thread.resume están en desuso?

En lugar de desplegar su propia bandera booleana, ¿por qué no usar simplemente el mecanismo de interrupción de hilos ya en los hilos de Java? Dependiendo de cómo se implementaron las partes internas en el código que no puede cambiar, es posible que también pueda cancelar parte de su ejecución.

Hilo exterior:

 if(oldThread.isRunning()) { oldThread.interrupt(); // Be careful if you're doing this in response to a user // action on the Event Thread // Blocking the Event Dispatch Thread in Java is BAD BAD BAD oldThread.join(); } oldThread = new Thread(someRunnable); oldThread.start(); 

Runnable / hilo interno:

 public void run() { // If this is all you're doing, interrupts and boolean flags may not work callExternalMethod(args); } public void run() { while(!Thread.currentThread().isInterrupted) { // If you have multiple steps in here, check interrupted peridically and // abort the while loop cleanly } } 

¿No es esto un poco como preguntar “¿Cómo puedo abortar un hilo cuando no hay otro método que no sea Thread.stop () disponible?”

Obviamente, la única respuesta válida es Thread.stop (). Es feo, podría romper las cosas en algunas circunstancias, puede conducir a memory leaks / recursos, y es mal visto por TLEJD (La Liga de Desarrolladores de Java Extraordinarios), sin embargo, todavía puede ser útil en algunos casos como este. Realmente no hay otro método si el código de terceros no tiene algún método cercano disponible.

OTOH, a veces hay métodos de cierre de puerta trasera. Es decir, cerrar un flujo subyacente con el que está trabajando o algún otro recurso que necesita para hacer su trabajo. Esto rara vez es mejor que simplemente llamar a Thread.stop () y dejar que experimente una excepción ThreadDeathException.

La respuesta aceptada a esta pregunta le permite enviar trabajos por lotes a un hilo de fondo. Este podría ser un mejor patrón para eso:

 public abstract class dispatcher extends Thread { protected abstract void processItem(T work); private List workItems = new ArrayList(); private boolean stopping = false; public void submit(T work) { synchronized(workItems) { workItems.add(work); workItems.notify(); } } public void exit() { stopping = true; synchronized(workItems) { workItems.notifyAll(); } this.join(); } public void run() { while(!stopping) { T work; synchronized(workItems) { if (workItems.empty()) { workItems.wait(); continue; } work = workItems.remove(0); } this.processItem(work); } } } 

Para usar esta clase, amplíelo, proporcionando un tipo para T y una implementación de processItem (). Luego solo construye uno y llama a start () en él.

Puede considerar agregar un método abortPending:

 public void abortPending() { synchronized(workItems) { workItems.clear(); } } 

para aquellos casos en los que el usuario saltó por delante del motor de renderizado y desea deshacerse del trabajo que se ha progtwigdo hasta el momento.

Un hilo se cerrará una vez que se haya run() método run() , por lo que necesita un control que lo haga terminar el método.

Puede interrumpir el hilo y luego hacer una comprobación que verifique periódicamente isInterrupted() y salga del método run() .

También puede usar un valor booleano que se comprueba periódicamente en el subproceso, y lo devuelve en ese caso, o poner el subproceso dentro de un bucle si realiza alguna tarea repetitiva y luego saldrá del método run () cuando configure el booleano. Por ejemplo,

 static boolean shouldExit = false; Thread t = new Thread(new Runnable() { public void run() { while (!shouldExit) { // do stuff } } }).start(); 

Desafortunadamente matar un hilo es intrínsecamente inseguro debido a las posibilidades de utilizar recursos que pueden sincronizarse mediante lockings y si el hilo que mata actualmente tiene un locking podría resultar en un punto muerto (bash constante de agarrar un recurso que no se puede obtener) . Deberá verificar manualmente si necesita eliminarse del hilo que desea detener. Volatile asegurará la verificación del verdadero valor de la variable en lugar de algo que pueda haberse almacenado previamente. En una nota al margen, Thread.join en el hilo de salida para asegurarse de que espere hasta que el hilo que se está muriendo se haya ido antes de hacer algo en lugar de verificar todo el tiempo.

Parece que no tienes ningún control sobre el hilo que está renderizando la pantalla, pero parece que tienes el control del componente giratorio. Deshabilitaría la ruleta mientras el hilo está renderizando la pantalla. De esta forma, el usuario al menos tiene algunos comentarios relacionados con sus acciones.

Sugiero que solo evite varios subprocesos mediante el uso de esperar y notificar, de modo que si el usuario cambia el valor muchas veces, solo ejecutará el subproceso una vez. Si los usuarios cambian el valor 10 veces, se disparará desde el subproceso en el primer cambio y luego todos los cambios realizados antes de que se complete el subproceso se “envían” en una sola notificación. Eso no detendrá un hilo, pero no hay buenas maneras de hacerlo en base a su descripción.

Las soluciones que tienen como objective el uso de un campo booleano son la dirección correcta. Pero el campo debe ser volátil. Java Language Spec dice :

“Por ejemplo, en el siguiente fragmento de código (roto), supongamos que this.done es un campo booleano no volátil:

  mientras (! this.done)
   Thread.sleep (1000);

El comstackdor puede leer el campo this.done una sola vez y reutilizar el valor en caché en cada ejecución del ciclo. Esto significaría que el ciclo nunca terminaría, incluso si otro hilo cambió el valor de this.done “.

Por lo que recuerdo, “Java Concurrency in Pratice” tiene como objective utilizar los métodos de interrupción () e interrumpido () de java.lang.Thread.

La forma en que he implementado algo como esto en el pasado es implementar un método shutdown() en mi subclase Runnable que establece una variable de instancia llamada should_shutdown en true. El método run() normalmente hace algo en un bucle, y verificará periódicamente should_shutdown y cuando es verdadero, regresa o llama a do_shutdown() y luego regresa.

Debe tener a mano una referencia al hilo de trabajo actual, y cuando el usuario cambie un valor, llame a shutdown() en el hilo actual y espere a que se cierre. Entonces puedes lanzar un nuevo hilo.

No recomendaría usar Thread.stop ya que estaba en desuso la última vez que lo verifiqué.

Editar:

Lea su comentario sobre cómo su subproceso de trabajo solo llama a otro método que tarda un tiempo en ejecutarse, por lo que lo anterior no se aplica. En este caso, sus únicas opciones reales son intentar llamar a interrupt() y ver si tiene algún efecto. De lo contrario, considere de alguna manera que causa manualmente la función a la que llama su hilo de trabajo para interrumpir. Por ejemplo, parece que está haciendo una representación compleja, por lo que quizás destruya el canvas y provoque una excepción. Esta no es una buena solución, pero hasta donde puedo decir, esta es la única manera de detener un hilo en juicios como este.

Como estás tratando con código al que no tienes acceso, probablemente no tengas suerte. El procedimiento estándar (como se describe en las otras respuestas) es tener un indicador que se verifica periódicamente por el hilo en ejecución. Si la bandera está configurada, haga la limpieza y salga.

Como esa opción no está disponible para usted, la única otra opción es forzar el cierre del proceso en ejecución. Esto solía ser posible al llamar a Thread.stop () , pero ese método ha quedado obsoleto permanentemente por el siguiente motivo (copiado de los javadocs):

Este método es inherentemente inseguro. Detener un hilo con Thread.stop hace que desbloquee todos los monitores que ha bloqueado (como consecuencia natural de la excepción ThreadDeath no verificada que se propaga por la stack). Si alguno de los objetos previamente protegidos por estos monitores se encontraba en un estado incoherente, los objetos dañados se vuelven visibles para otros hilos, lo que puede generar un comportamiento arbitrario.

Más información sobre este tema se puede encontrar aquí .

Una forma absolutamente segura de que pueda realizar su pedido (aunque esta no es una forma muy eficiente de hacerlo) es iniciar un nuevo proceso java a través de Runtime.exec () y luego detener ese proceso según sea necesario a través de Process.destroy () . Sin embargo, compartir el estado entre procesos como este no es exactamente trivial.

En lugar de jugar con el inicio y la detención de un hilo, ¿ha considerado que el hilo observe las propiedades que está cambiando a través de su interfaz? En algún momento, todavía querrás una condición de detención para tu hilo, pero esto puede hacerse así. Si eres fanático de MVC, esto encaja muy bien en ese tipo de diseño

Lo siento, después de volver a leer su pregunta, ni esta ni ninguna de las otras sugerencias de ‘variable de verificación’ resolverán su problema.

La respuesta correcta es no usar un hilo.

Deberías usar ejecutores, ver el paquete: java.util.concurrent

Tal vez esto pueda ayudarlo: ¿cómo podemos eliminar un hilo en ejecución en Java?

Puede eliminar un hilo en particular configurando una variable de clase externa.

  Class Outer { public static flag=true; Outer() { new Test().start(); } class Test extends Thread { public void run() { while(Outer.flag) { //do your work here } } } } 

si desea detener el hilo anterior, establezca la variable de indicador en false . La otra forma de matar un hilo es simplemente registrarlo en ThreadGroup, luego llamar a destroy() . De esta forma también se puede usar para eliminar subprocesos similares al crearlos como grupo o registrarse con un grupo.