2009-03-31 13 views
100

Tengo un objeto con un método llamado StartDownload(), que inicia tres hilos.¿Cómo saber si otros hilos han terminado?

¿Cómo obtengo una notificación cuando cada subproceso ha terminado de ejecutarse?

¿Hay alguna manera de saber si uno (o todos) del subproceso está terminado o todavía se está ejecutando?

+1

Tome un vistazo a la de Java 5 [clase Barrera] (http: //java.sun. com/j2se/1.5.0/docs/api/java/util/concurrent/CyclicBarrier.html) – Fortyrunner

Respuesta

198

Hay un número de maneras que usted puede hacer esto:

  1. Uso Thread.join() en su hilo principal que esperar en un modo de bloqueo para cada hilo para completar, o
  2. Comprobar Thread.isAlive() de una forma de votación - - generalmente desaconsejado - esperar hasta que cada subproceso se haya completado o
  3. poco ortodoxo, para cada subproceso en cuestión, llame al setUncaughtExceptionHandler para llamar a un método en su objeto, y programe cada subproceso para lanzar una excepción no detectada cuando finalice o
  4. Use bloqueos o sincronizadores o mecanismos de java.util.concurrent o
  5. Más ortodoxo, cree un oyente en su Tema principal y luego programe cada uno de sus Subprocesos para decirle al oyente que se han completado.

¿Cómo implementar Idea # 5? Bueno, una forma es crear primero una interfaz:

public interface ThreadCompleteListener { 
    void notifyOfThreadComplete(final Thread thread); 
} 

continuación, crear la clase siguiente:

public abstract class NotifyingThread extends Thread { 
    private final Set<ThreadCompleteListener> listeners 
        = new CopyOnWriteArraySet<ThreadCompleteListener>(); 
    public final void addListener(final ThreadCompleteListener listener) { 
    listeners.add(listener); 
    } 
    public final void removeListener(final ThreadCompleteListener listener) { 
    listeners.remove(listener); 
    } 
    private final void notifyListeners() { 
    for (ThreadCompleteListener listener : listeners) { 
     listener.notifyOfThreadComplete(this); 
    } 
    } 
    @Override 
    public final void run() { 
    try { 
     doRun(); 
    } finally { 
     notifyListeners(); 
    } 
    } 
    public abstract void doRun(); 
} 

y luego cada uno de sus hilos se extenderá NotifyingThread y en lugar de implementar run() se pondrá en marcha doRun(). Por lo tanto, cuando completen, automáticamente notificarán a cualquier persona que esté esperando recibir una notificación.

Finalmente, en su clase principal - la que inicia todos los subprocesos (o al menos el objeto esperando notificación) - modifique esa clase a implement ThreadCompleteListener e inmediatamente después de crear cada subproceso añádase a la lista de oyentes:

NotifyingThread thread1 = new OneOfYourThreads(); 
thread1.addListener(this); // add ourselves as a listener 
thread1.start();   // Start the Thread 

entonces, a medida que cada hilo termina, su método de notifyOfThreadComplete se invocará a la instancia de hilo que acaba de terminar (o se haya estrellado).

Tenga en cuenta que sería mejor que implements Runnable en lugar de extends Thread para NotifyingThread, ya que la extensión de subprocesos generalmente no se recomienda en el nuevo código. Pero estoy codificando tu pregunta. Si cambia la clase NotifyingThread para implementar Runnable, debe cambiar parte del código que gestiona Threads, lo que es bastante sencillo de hacer.

+0

¡Hola! Me gusta la última idea Yo para implementar un oyente para hacer eso? Gracias –

+5

Actualicé mi respuesta para agregar un ejemplo de código. – Eddie

+2

pero utilizando este enfoque, se llama a los NotifiyListeners dentro de run() por lo que se llamará insisde the thread y las llamadas adicionales se realizarán allí también, ¿no es así? –

4

¿Quieres esperar a que terminen? Si es así, usa el método de unión.

También existe la propiedad isAlive si solo desea comprobarlo.

+2

Tenga en cuenta que isAlive devuelve falso si el subproceso no se ha iniciado aún (incluso si su propio subproceso ya ha llamado al inicio) . –

0

Mire los documentos java para la clase Thread. Puede verificar el estado del hilo. Si coloca los tres hilos en las variables miembro, entonces los tres hilos pueden leer los estados de los demás. Tienes que ser un poco cuidadoso, porque puedes causar condiciones de carrera entre los hilos. Solo trata de evitar una lógica complicada basada en el estado de los otros hilos. Definitivamente evitar múltiples hilos escribiendo en las mismas variables.

4

Puede interrogar a la instancia hilo con getState() que devuelve una instancia de Thread.State enumeración con uno de los siguientes valores:

* NEW 
    A thread that has not yet started is in this state. 
* RUNNABLE 
    A thread executing in the Java virtual machine is in this state. 
* BLOCKED 
    A thread that is blocked waiting for a monitor lock is in this state. 
* WAITING 
    A thread that is waiting indefinitely for another thread to perform a particular action is in this state. 
* TIMED_WAITING 
    A thread that is waiting for another thread to perform an action for up to a specified waiting time is in this state. 
* TERMINATED 
    A thread that has exited is in this state. 

Sin embargo, creo que sería un diseño mejor tener un maestro hilo que espera que los 3 niños terminen, el maestro continuará la ejecución cuando los otros 3 hayan terminado.

+0

Esperar a que salgan los 3 niños puede no ajustarse al paradigma de uso. Si se trata de un administrador de descargas, es posible que quieran iniciar 15 descargas y simplemente eliminar el estado de la barra de estado o alertar al usuario cuando se haya completado una descarga, en cuyo caso una devolución de llamada funcionaría mejor. – digitaljoel

2

Sugeriría mirar el javadoc para la clase Thread.

Tiene múltiples mecanismos para la manipulación de hilos.

  • Su hilo principal podría join() los tres hilos en serie, y luego no procedería hasta que los tres se hacen.

  • Realice una encuesta del estado del subproceso de los subprocesos generados a intervalos.

  • poner todos los hilos generados en una separada ThreadGroup y sondear el activeCount() en el ThreadGroup y esperar a que se haga a 0.

  • instalación de una devolución de llamada personalizado o tipo de oyente de interfaz para la comunicación entre hilos .

Estoy seguro de que todavía faltan muchas otras formas.

11

solución utilizando CyclicBarrier

public class Downloader { 
    private CyclicBarrier barrier; 
    private final static int NUMBER_OF_DOWNLOADING_THREADS; 

    private DownloadingThread extends Thread { 
    private final String url; 
    public DownloadingThread(String url) { 
     super(); 
     this.url = url; 
    } 
    @Override 
    public void run() { 
     barrier.await(); // label1 
     download(url); 
     barrier.await(); // label2 
    } 
    } 
    public class startDownload() { 
    // plus one for the main thread of execution 
    barrier = new CyclicBarrier(NUMBER_OF_DOWNLOADING_THREADS + 1); // label0 
    for (int i = 0; i < NUMBER_OF_DOWNLOADING_THREADS; i++) { 
     new DownloadingThread("http://www.flickr.com/someUser/pic" + i + ".jpg").start(); 
    } 
    barrier.await(); // label3 
    displayMessage("Please wait..."); 
    barrier.await(); // label4 
    displayMessage("Finished"); 
    } 
} 

label0 - cíclico barrera se crea con número de partes igual al número de hilos de ejecución más uno para el hilo principal de ejecución (en la que startDownload() es siendo ejecutado)

etiqueta 1 - n-ésimo DownloadingThread entra en la sala de espera

label 3 - NUMBER_OF_DOWNLOADING_THREADS han ingresado a la sala de espera. El hilo principal de ejecución los libera para comenzar a hacer sus trabajos de descarga más o menos al mismo tiempo

etiqueta 4 - el hilo principal de ejecución entra en la sala de espera. Esta es la parte más "complicada" del código para entender. No importa qué hilo ingresará a la sala de espera por segunda vez. Es importante que el hilo que ingrese a la sala por última vez asegure que todos los demás subprocesos de descarga hayan terminado sus trabajos de descarga.

label 2 - n-th DownloadingThread ha terminado su trabajo de descarga y entra en la sala de espera. Si es el último, es decir, ya lo han ingresado NUMBER_OF_DOWNLOADING_THREADS, incluido el hilo principal de ejecución, el hilo principal continuará su ejecución solo cuando todos los demás hilos hayan terminado de descargarse.

0

También podría usar SwingWorker, que tiene soporte para el cambio de propiedades incorporado. Consulte addPropertyChangeListener() o el método get() para un ejemplo de detector de cambio de estado.

1

También podría usar el objeto Executors para crear un grupo de subprocesos ExecutorService. Luego use el método invokeAll para ejecutar cada uno de sus hilos y recuperar Futuros. Esto se bloqueará hasta que todos hayan terminado la ejecución. Su otra opción sería ejecutar cada uno usando el grupo y luego llamar al awaitTermination para bloquear hasta que el grupo termine de ejecutarse. Solo asegúrese de llamar al shutdown() cuando termine de agregar tareas.

7

Usted debe realmente prefiere una solución que utiliza java.util.concurrent. Encuentra y lee a Josh Bloch y/o Brian Goetz sobre el tema.

Si no está usando java.util.concurrent.* y se responsabiliza de usar Threads directamente, entonces probablemente debería usar join() para saber cuándo se realiza un thread. Aquí hay un mecanismo de devolución de llamada súper simple. En primer lugar ampliar la interfaz Runnable tener una devolución de llamada:

public interface CallbackRunnable extends Runnable { 
    public void callback(); 
} 

A continuación, crea un ejecutor que ejecutará el ejecutable y volver a llamar cuando se hace.

public class CallbackExecutor implements Executor { 

    @Override 
    public void execute(final Runnable r) { 
     final Thread runner = new Thread(r); 
     runner.start(); 
     if (r instanceof CallbackRunnable) { 
      // create a thread to perform the callback 
      Thread callerbacker = new Thread(new Runnable() { 
       @Override 
       public void run() { 
        try { 
         // block until the running thread is done 
         runner.join(); 
         ((CallbackRunnable)r).callback(); 
        } 
        catch (InterruptedException e) { 
         // someone doesn't want us running. ok, maybe we give up. 
        } 
       } 
      }); 
      callerbacker.start(); 
     } 
    } 

} 

El otro tipo de cosa obvia para agregar a su interfaz de CallbackRunnable es un medio para manejar las excepciones, así que tal vez puso una línea public void uncaughtException(Throwable e); allí y en su ejecutor, instale un Thread.UncaughtExceptionHandler que le envíe a ese método de interfaz.

Pero haciendo todo lo que realmente comienza a oler como java.util.concurrent.Callable. Deberías mirar realmente usando java.util.concurrent si tu proyecto lo permite.

1

Muchas cosas han cambiado en los últimos 6 años en frente multi-threading.

En lugar de utilizar join() y la API de bloqueo, puede utilizar

1. ExecutorServiceinvokeAll() API

ejecuta las tareas encomendadas, devolviendo una lista de futuros que sostienen su estado y los resultados cuando todo completa.

2. CountDownLatch

Una ayuda de sincronización que permite a uno o más hilos que esperar hasta que un conjunto de operaciones que se realiza en otros hilos completa.

A CountDownLatch se inicializa con un conteo dado.El bloque de métodos de espera hasta que el recuento actual llega a cero debido a las invocaciones del método countDown(), después del cual se liberan todos los subprocesos en espera y las invocaciones posteriores de espera vuelven inmediatamente. Este es un fenómeno de una sola vez: el recuento no se puede restablecer. Si necesita una versión que restablece el recuento, considere usar un CyclicBarrier.

3. ForkJoinPool o newWorkStealingPool() en Executors es otra manera

4.Iterate través de todas las tareas de Future presentar en ExecutorService y comprobar el estado de bloqueo de llamadas con get() en Future objeto

un vistazo a preguntas relacionadas de SE:

How to wait for a thread that spawns it's own thread?

Executors: How to synchronously wait until all tasks have finished if tasks are created recursively?

1

Aquí hay una solución que es simple, corta, fácil de entender y funciona perfectamente para mí. Necesitaba dibujar a la pantalla cuando termina otro hilo; pero no pudo porque el hilo principal tiene el control de la pantalla. Entonces:

(1) Creé la variable global: boolean end1 = false; El hilo lo establece en verdadero cuando termina. Esto se recoge en el hilo principal mediante el bucle "postDelayed", donde se responde.

(2) Mi hilo contiene:

void myThread() { 
    end1 = false; 
    new CountDownTimer(((60000, 1000) { // milliseconds for onFinish, onTick 
     public void onFinish() 
     { 
      // do stuff here once at end of time. 
      end1 = true; // signal that the thread has ended. 
     } 
     public void onTick(long millisUntilFinished) 
     { 
      // do stuff here repeatedly. 
     } 
    }.start(); 

} 

(3) Afortunadamente, "postDelayed" se ejecuta en el hilo principal, de modo que es donde el registro de entrada el otro hilo una vez cada segundo. Cuando termina el otro hilo, puede comenzar lo que queramos hacer a continuación.

Handler h1 = new Handler(); 

private void checkThread() { 
    h1.postDelayed(new Runnable() { 
     public void run() { 
     if (end1) 
      // resond to the second thread ending here. 
     else 
      h1.postDelayed(this, 1000); 
     } 
    }, 1000); 
} 

(4) Por último, empezar todo el asunto corriendo en algún lugar de su código llamando:

void startThread() 
{ 
    myThread(); 
    checkThread(); 
} 
Cuestiones relacionadas