¿Cómo saber si otros hilos han terminado?

126

Tengo un objeto con un método llamado StartDownload() , que inicia tres hilos.

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

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

Ricardo Felgueiras
fuente
1
Echa un vistazo a la clase de barrera
Fortyrunner

Respuestas:

228

Hay varias formas de hacerlo:

  1. Use Thread.join () en su hilo principal para esperar bloqueando a que se complete cada hilo, o
  2. Verifique Thread.isAlive () en forma de sondeo, generalmente desaconsejado, para esperar hasta que cada subproceso se haya completado, o
  3. Poco ortodoxo, para cada subproceso en cuestión, llame a setUncaughtExceptionHandler para llamar a un método en su objeto y programe cada subproceso para lanzar una excepción no capturada cuando se complete, o
  4. Utilice bloqueos o sincronizadores o mecanismos de java.util.concurrent , o
  5. Más ortodoxo, cree un oyente en su Hilo principal y luego programe cada uno de sus Hilos para decirle al oyente que se han completado.

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

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

luego cree la siguiente clase:

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á NotifyingThready en lugar de implementarlo run()se implementará doRun(). Por lo tanto, cuando se completen, notificarán automáticamente a cualquiera que esté esperando la notificación.

Finalmente, en su clase principal, la que inicia todos los subprocesos (o al menos el objeto que espera la notificación), modifique esa clase implement ThreadCompleteListenere inmediatamente después de crear cada subproceso se agrega a la lista de oyentes:

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

luego, a medida que sale cada subproceso, notifyOfThreadCompletese invocará su método con la instancia de subproceso que acaba de completarse (o bloquearse).

Tenga en cuenta que sería mejor que implements Runnableen vez de extends Threadpara NotifyingThreadcomo hilo que se extiende por lo general se desaconseja en nuevo código. Pero estoy codificando tu pregunta. Si cambia la NotifyingThreadclase para implementar, Runnableentonces tiene que cambiar parte de su código que administra los subprocesos, lo cual es bastante sencillo de hacer.

Eddie
fuente
¡¡Hola!! Me gusta la ultima idea Yo para implementar un oyente para hacer eso? Gracias
Ricardo Felgueiras
44
pero usando este enfoque, notifiyListeners se llama dentro de run (), por lo que se llamará dentro del hilo y las llamadas adicionales se realizarán allí también, ¿no es así?
Jordi Puigdellívol
1
@Eddie Jordi preguntaba, si es posible llamar al notifymétodo, no en el runmétodo, sino después.
Tomasz Dzięcielewski
1
La pregunta realmente es: ¿cómo te desconectas del hilo secundario ahora? Sé que está terminado, pero ¿cómo puedo acceder al hilo principal ahora?
Patrick
1
¿Es seguro este hilo? Parece que se llamará a notifyListeners (y, por tanto, notifyOfThreadComplete) dentro de NotifyingThread, en lugar de hacerlo dentro del hilo que creó el propio oyente.
Aaron
13

Solución usando 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 void 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 : la barrera cíclica se crea con un número de partes igual al número de subprocesos en ejecución más uno para el subproceso principal de ejecución (en el que se ejecuta startDownload ())

etiqueta 1 - n-th DownloadingThread entra en la sala de espera

etiqueta 3 : NUMBER_OF_DOWNLOADING_THREADS han entrado en la sala de espera. El hilo principal de ejecución los libera para comenzar a realizar sus tareas de descarga más o menos al mismo tiempo

etiqueta 4 : el hilo principal de ejecución ingresa a 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 cualquier subproceso que entre en la sala al final garantice que todos los otros subprocesos de descarga hayan finalizado sus trabajos de descarga.

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

Boris Pavlović
fuente
9

Realmente debería preferir una solución que utilice java.util.concurrent. Encuentra y lee a Josh Bloch y / o Brian Goetz sobre el tema.

Si no está utilizando java.util.concurrent.*y está asumiendo la responsabilidad de usar subprocesos directamente, entonces probablemente debería join()saber cuándo se realiza un subproceso. Aquí hay un mecanismo de devolución de llamada súper simple. Primero extienda la Runnableinterfaz para tener una devolución de llamada:

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

Luego crea un Ejecutor que ejecutará tu ejecutable y te devolverá la llamada cuando termine.

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 CallbackRunnableinterfaz es un medio para manejar cualquier excepción, así que tal vez ponga una public void uncaughtException(Throwable e);línea allí y en su ejecutor, instale un Thread.UncaughtExceptionHandler para enviarlo a ese método de interfaz.

Pero hacer todo lo que realmente comienza a oler java.util.concurrent.Callable. Realmente debería considerar el uso java.util.concurrentsi su proyecto lo permite.

broc.seib
fuente
No estoy un poco claro sobre lo que gana con este mecanismo de devolución de llamada frente a simplemente llamar runner.join()y luego cualquier código que desee después de eso, ya que sabe que el hilo ha terminado. ¿Es solo que puedes definir ese código como una propiedad de los ejecutables, para que puedas tener diferentes cosas para diferentes ejecutables?
Stephen
2
Sí, runner.join()es la forma más directa de esperar. Asumí que el OP no quería bloquear su hilo principal de llamadas ya que pidieron ser "notificados" para cada descarga, lo que podría completarse en cualquier orden. Esto ofreció una forma de recibir notificaciones de forma asincrónica.
broc.seib
4

¿Quieres esperar a que terminen? Si es así, use el método Join.

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

Jonathan Allen
fuente
3
Tenga en cuenta que isAlive devuelve falso si el hilo no ha comenzado a ejecutarse todavía (incluso si su propio hilo ya ha llamado a iniciar en él).
Tom Hawtin - tackline el
@ TomHawtin-tackline, ¿estás seguro de eso? Contradeciría la documentación de Java ("Un hilo está vivo si se ha iniciado y aún no ha muerto" - docs.oracle.com/javase/6/docs/api/java/lang/… ). También contradiría las respuestas aquí ( stackoverflow.com/questions/17293304/… )
Stephen
@Stephen Hace mucho tiempo desde que escribí eso, pero parece ser cierto. Me imagino que causó problemas a otras personas que estaban frescos en mi memoria hace nueve años. Lo que se observa exactamente dependerá de la implementación. Le dice a Threadto start, que hace el hilo, pero la llamada vuelve inmediatamente. isAlivedebería ser una simple prueba de bandera, pero cuando busqué en Google el método fue native.
Tom Hawtin - tackline
4

Puede interrogar la instancia de subproceso con getState () que devuelve una instancia de enumeración Thread.State 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 mejor diseño tener un hilo maestro que espere a que los 3 hijos terminen, el maestro continuará la ejecución cuando los otros 3 hayan terminado.

Miquel
fuente
Esperar a que salgan los 3 niños puede no encajar en el paradigma de uso. Si se trata de un administrador de descargas, es posible que deseen 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
3

También podría usar el Executorsobjeto para crear un grupo de subprocesos ExecutorService . Luego use el invokeAllmétodo 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 awaitTerminationpara bloquear hasta que el grupo termine de ejecutarse. Solo asegúrese de llamar shutdown() cuando termine de agregar tareas.

J Gitter
fuente
2

Muchas cosas han cambiado en los últimos 6 años en el frente de subprocesos múltiples.

En lugar de usar join()y bloquear la API, puede usar

1. API de ExecutorService invokeAll()

Ejecuta las tareas dadas, devolviendo una lista de Futuros con su estado y resultados cuando todo esté completo.

2. CountDownLatch

Una ayuda de sincronización que permite que uno o más subprocesos esperen hasta que se complete un conjunto de operaciones que se realizan en otros subprocesos.

A CountDownLatchse inicializa con un recuento dado. Los métodos de espera se bloquean hasta que el recuento actual llega a cero debido a invocaciones del countDown()método, después de lo cual se liberan todos los subprocesos de espera y cualquier invocación posterior de espera regresa de inmediato. Este es un fenómeno de una sola vez: el conteo no se puede restablecer. Si necesita una versión que restablezca el recuento, considere usar un CyclicBarrier.

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

4.Interate a través de todas las Futuretareas desde el envío ExecutorServicey verifica el estado bloqueando la llamada get()en el Futureobjeto

Eche un vistazo a las preguntas SE relacionadas:

¿Cómo esperar un hilo que genera su propio hilo?

Ejecutores: ¿Cómo esperar sincrónicamente hasta que todas las tareas hayan terminado si las tareas se crean de forma recursiva?

Ravindra babu
fuente
2

Sugeriría mirar el javadoc para la clase Thread .

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

  • Su hilo principal podría ser join()los tres hilos en serie, y luego no continuaría hasta que los tres hayan terminado.

  • Encuesta el estado del hilo de los hilos generados a intervalos.

  • Coloca todos los hilos generados en un separado ThreadGroupy sondea activeCount()en el ThreadGroupy espera a que llegue a 0.

  • Configure una devolución de llamada personalizada o un tipo de interfaz de escucha para la comunicación entre subprocesos.

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

digitaljoel
fuente
1

Aquí hay una solución simple, corta, fácil de entender y que funciona perfectamente para mí. Necesitaba dibujar en la pantalla cuando otro hilo termina; 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 finaliza. Eso es recogido en el hilo principal por 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, por lo que es allí donde se verifica el otro hilo una vez por segundo. Cuando termina el otro hilo, esto 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) Finalmente, comience a ejecutar todo en algún lugar de su código llamando a:

void startThread()
{
   myThread();
   checkThread();
}
DreamMaster Pro
fuente
1

Supongo que la forma más fácil es usar la ThreadPoolExecutorclase.

  1. Tiene una cola y puede establecer cuántos subprocesos deberían funcionar en paralelo.
  2. Tiene buenos métodos de devolución de llamada:

Métodos de gancho

Esta clase proporciona métodos reemplazables beforeExecute(java.lang.Thread, java.lang.Runnable)y protegidos afterExecute(java.lang.Runnable, java.lang.Throwable)que se llaman antes y después de la ejecución de cada tarea. Estos pueden usarse para manipular el entorno de ejecución; por ejemplo, reinicializando ThreadLocals, recopilando estadísticas o agregando entradas de registro. Además, terminated()se puede anular el método para realizar cualquier procesamiento especial que deba realizarse una vez que el ejecutor haya finalizado por completo.

que es exactamente lo que necesitamos Anularemos afterExecute()para obtener devoluciones de llamada después de que se realice cada subproceso y anularemos terminated()para saber cuándo se realizan todos los subprocesos.

Entonces esto es lo que debes hacer

  1. Crear un ejecutor:

    private ThreadPoolExecutor executor;
    private int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors();    
    
    
    
    private void initExecutor() {
    
    executor = new ThreadPoolExecutor(
            NUMBER_OF_CORES * 2,  //core pool size
            NUMBER_OF_CORES * 2, //max pool size
            60L, //keep aive time
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<Runnable>()
    ) {
    
        @Override
        protected void afterExecute(Runnable r, Throwable t) {
            super.afterExecute(r, t);
                //Yet another thread is finished:
                informUiAboutProgress(executor.getCompletedTaskCount(), listOfUrisToProcess.size());
            }
        }
    
    };
    
        @Override
        protected void terminated() {
            super.terminated();
            informUiThatWeAreDone();
        }
    
    }
  2. Y comienza tus hilos:

    private void startTheWork(){
        for (Uri uri : listOfUrisToProcess) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    doSomeHeavyWork(uri);
                }
            });
        }
        executor.shutdown(); //call it when you won't add jobs anymore 
    }

Dentro del método, informUiThatWeAreDone();haga lo que necesite hacer cuando todos los hilos estén terminados, por ejemplo, actualice la IU.

NOTA: No se olvide de usar synchronizedmétodos, ya que hace su trabajo en paralelo y TENGA MUY PRECAUCIÓN si decide llamar al synchronizedmétodo desde otrosynchronized método! Esto a menudo conduce a puntos muertos

¡Espero que esto ayude!

Kirill Karmazin
fuente
0

También puede usar SwingWorker, que tiene soporte de cambio de propiedad incorporado. Consulte addPropertyChangeListener () o el método get () para ver un ejemplo de escucha de cambio de estado.

akarnokd
fuente
0

Mire la documentación de Java para la clase Thread. Puede verificar el estado del hilo. Si coloca los tres hilos en las variables miembro, los tres hilos pueden leer los estados de cada uno.

Sin embargo, debes tener un poco de cuidado, ya que puedes causar condiciones de carrera entre los hilos. Solo trate de evitar una lógica complicada basada en el estado de los otros hilos. Definitivamente evite que varios hilos escriban en las mismas variables.

Don Kirkby
fuente