Cómo abortar un hilo de forma 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 un spinner, por ejemplo). Cada vez que se cambia uno de estos parámetros, lanzo 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 de nuevo haciendo clic muchas veces en la flecha del spinner) 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 del parámetro.

¿Cómo puedo hacer algo así?

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

Author: jumar, 2008-09-18

15 answers

Intenta interrumpir() como algunos han dicho para ver si hace alguna diferencia en tu hilo. Si no, intente destruir o cerrar un recurso que hará que el hilo se detenga. Eso tiene una oportunidad de ser un poco mejor que tratar de lanzar hilo.stop() en él.

Si el rendimiento es tolerable, es posible que vea cada actualización 3D como un evento discreto no interrumpible y simplemente deje que se ejecute hasta la conclusión, verificando después si hay una nueva actualización más reciente que realizar. Esto podría hacer que la GUI a poco entrecortado 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, luego ver el resultado de su último cambio. Pero dependiendo de la duración de este proceso, podría ser tolerable, y evitaría tener que matar el hilo. El diseño podría tener este aspecto:

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;
}
 12
Author: skiphoppy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-11-19 20:16:09

El hilo que está actualizando la vista 3D debe comprobar periódicamente alguna bandera (use un volatile boolean) para ver si debe terminar. Cuando quieras abortar el hilo, simplemente establece la bandera. Cuando el hilo siguiente comprueba la bandera, simplemente debe romper el bucle que está utilizando para actualizar la vista y volver desde su método run.

Si realmente no puede acceder al código que se está ejecutando para que marque una bandera, entonces no hay una forma segura de detener el hilo. ¿Este Hilo alguna vez terminar normalmente antes de que se complete su solicitud? Si es así, ¿qué hace que se detenga?

Si se ejecuta durante un largo período de tiempo, y simplemente debe terminarlo, puede considerar usar el método Thread.stop() obsoleto. Sin embargo, fue obsoleto por una buena razón. Si ese hilo se detiene mientras está en medio de alguna operación que deja algo en un estado inconsistente 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 hilo.dejar hace que desbloquee todos los monitores que ha bloqueado (como un natural consequence of the unchecked Propagación de la excepción ThreadDeath pila). Si alguno de los objetos previamente protegidos por estos monitores estaban en un estado inconsistente, el objetos dañados se hacen visibles para otros hilos, potencialmente resultantes en un comportamiento arbitrario. Muchos usos de dejar debe sustituirse por un código que simplemente modifica alguna variable a indicar que el hilo de destino debe deja de correr. El hilo de destino debe compruebe esta variable regularmente, y retorno de su método run en un de manera ordenada si la variable indica que es dejar de correr. Si el hilo de destino espera mucho tiempo períodos (en una variable de condición, para ejemplo), el método de interrupción debe se utiliza para interrumpir la espera. Para más información, ver Hilo.detente, Thread.suspender y Hilo.curriculum vitae obsoleto?

 8
Author: Dave L.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-18 16:25:22

En lugar de lanzar su propia bandera booleana, ¿por qué no usar el mecanismo de interrupción de subprocesos ya en subprocesos Java? Dependiendo de cómo se implementaron los internos en el código que no puede cambiar, también puede abortar 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();

Interior Runnable / Thread:

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
    }
}
 4
Author: basszero,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-07-01 10:04:15

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

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

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 ThreadDeath, sin embargo.

 3
Author: jsight,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-18 16:58:22

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

public abstract class dispatcher<T> extends Thread {

  protected abstract void processItem(T work);

  private List<T> workItems = new ArrayList<T>();
  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, extiéndala, proporcionando un tipo para T y una implementación de processItem(). Entonces simplemente construye uno y llama a start () sobre él.

Podría considerar agregar un método abortPending:

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

Para aquellos casos en los que el usuario se ha saltado por delante del motor de renderizado y desea desechar el trabajo que tiene ha sido programado hasta ahora.

 2
Author: nsayer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-10-27 17:28:00

Un hilo saldrá una vez que se complete el método run(), por lo que necesita una verificación que lo hará terminar el método.

Puede interrumpir el subproceso, y luego tener alguna comprobación que verificaría periódicamente isInterrupted() y regresaría del método run().

También puedes usar un booleano que se comprueba periódicamente dentro del subproceso, y lo hace regresar si es así, o poner el subproceso dentro de un bucle si está haciendo alguna tarea repetativa y luego saldrá del método run () cuando establezcas el booleano. Por ejemplo,

static boolean shouldExit = false;
Thread t = new Thread(new Runnable() {
    public void run() {
        while (!shouldExit) {
            // do stuff
        }
    }
}).start();
 1
Author: Rich Adams,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-18 16:19:32

Desafortunadamente matar un hilo es inherentemente inseguro debido a las posibilidades de usar recursos que se pueden sincronizar por bloqueos y si el hilo que matas actualmente tiene un bloqueo podría resultar en que el programa entre en un punto muerto (intento constante de agarrar un recurso que no se puede obtener). Tendrá que comprobar manualmente si necesita ser eliminado del hilo que desea detener. Volatile se asegurará de verificar el valor verdadero de la variable en lugar de algo que pueda haber sido almacenado previamente. En un hilo nota lateral.únase al hilo de salida para asegurarse de que espere hasta que el hilo de muerte se haya ido antes de hacer nada en lugar de verificar todo el tiempo.

 1
Author: bmeck,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-18 16:23:13

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

 1
Author: Javamann,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-18 18:31:25

Le sugiero que evite múltiples subprocesos utilizando wait and notify para que si el usuario cambia el valor muchas veces solo ejecute el Subproceso una vez. Si los usuarios cambian el valor 10 veces, se activará el Hilo en el primer cambio y luego cualquier cambio realizado antes de que el Hilo se realice, todos se "enrollarán" en una notificación. Eso no detendrá un Hilo, pero no hay buenas maneras de hacerlo según tu descripción.

 1
Author: James A. N. Stauffer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-18 18:52:46

Las soluciones que tienen como propósito el uso de un campo booleano son la dirección correcta. Pero el campo debe ser volátil. La Especificación del lenguaje Java dice :

"Por ejemplo, en el siguiente fragmento de código (roto), supongamos que esto.hecho es un no- campo booleano volátil:

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

El compilador es libre de leer el campo esto.hecho solo una vez, y reutilizar el valor almacenado en caché en cada ejecución del bucle. Esto significaría que el bucle nunca terminaría, incluso si otro hilo cambió el valor de esto.Terminado."

Por lo que recuerdo "Java Concurrency in Pratice" propósitos para usar los métodos interrupt() y interrupted() de java.lang.Hilo.

 1
Author: dmeister,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-19 22:01:14

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 sea true, devuelve, o llama a do_shutdown() y luego devuelve.

Debe mantener 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 apague. Luego puedes lanzar un nuevo hilo.

No recomendaría usar Thread.stop ya que fue obsoleto la última vez que lo comprobé.

Editar:

Lea su comentario sobre cómo su hilo 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. Si no es así, considere de alguna manera hacer que se rompa manualmente la función que su hilo de trabajo está llamando. Por ejemplo, suena como si estuviera haciendo un renderizado complejo, así que tal vez destruya el lienzo y haga que lance una excepción. Esta no es una buena solución, pero por lo que puedo decir, esta es la única manera de detener un hilo en trajes como este.

 1
Author: freespace,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-12-10 00:23:28

Dado que se trata de código que no tiene acceso a usted es probablemente de suerte. El procedimiento estándar (como se describe en las otras respuestas) es tener una bandera que se comprueba periódicamente por el hilo en ejecución. Si la bandera está establecida, haga limpieza y salga.

Dado que 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 llamando a Thread.stop () , pero ese método ha sido permanentemente obsoleto para el siguiente razón (copiado de los javadocs):

Este método es inherentemente inseguro. Detener un hilo con hilo.stop hace que desbloquee todos los monitores que ha bloqueado (como consecuencia natural de la excepción ThreadDeath sin marcar que se propaga por la pila). Si alguno de los objetos previamente protegidos por estos monitores estaba en un estado inconsistente, los objetos dañados se vuelven visibles para otros subprocesos, lo que podría resultar en un comportamiento arbitrario.

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

Una forma absolutamente segura de cumplir con su solicitud (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.

 0
Author: toluju,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-18 16:42:39

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

Lo sentimos, después de volver a leer su pregunta, ni esta ni ninguna de las otras sugerencias de 'comprobar variable' resolverán su problema.

 0
Author: Nerdfest,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-18 17:05:48

La respuesta correcta es no usar un hilo.

Debería usar ejecutores, vea el paquete: java.útil.concurrente

 0
Author: Pyrolistical,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-10-30 17:16:21

Tal vez esto pueda ayudarte: ¿Cómo podemos matar un hilo en ejecución en Java?

Puede matar un hilo en particular estableciendo 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 subproceso anterior, establezca la variable flag en false. La otra forma de matar un hilo es simplemente registrarlo en ThreadGroup, luego llamar a destroy(). De esta manera también se puede usar para matar hilos similares creándolos como grupo o registrándolos con grupo.

 0
Author: Olvagor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-03-24 20:44:54