¿Cómo hacer que un hilo Java espere la salida de otro hilo?


128

Estoy haciendo una aplicación Java con un hilo de lógica de aplicación y un hilo de acceso a la base de datos. Ambos persisten durante toda la vida útil de la aplicación y ambos deben ejecutarse al mismo tiempo (uno habla con el servidor, uno habla con el usuario; cuando la aplicación se inicia por completo, necesito que ambos funcionen).

Sin embargo, en el inicio, necesito asegurarme de que inicialmente el subproceso de la aplicación espere hasta que el subproceso db esté listo (actualmente determinado mediante una encuesta de un método personalizado dbthread.isReady()). No me importaría si el hilo de la aplicación se bloquea hasta que el hilo de db esté listo.

Thread.join() no parece una solución: el subproceso db solo se cierra al cerrar la aplicación.

while (!dbthread.isReady()) {} funciona, pero el bucle vacío consume muchos ciclos de procesador.

¿Alguna otra idea? Gracias.

Respuestas:


128

Realmente recomendaría que revises un tutorial como Sun's Java Concurrency antes de comenzar en el mágico mundo de los subprocesos múltiples.

También hay una serie de buenos libros (google para "Programación concurrente en Java", "Concurrencia de Java en la práctica".

Para llegar a su respuesta:

En su código que debe esperar el dbThread, debe tener algo como esto:

//do some work
synchronized(objectYouNeedToLockOn){
    while (!dbThread.isReady()){
        objectYouNeedToLockOn.wait();
    }
}
//continue with work after dbThread is ready

En su dbThreadmétodo, necesitaría hacer algo como esto:

//do db work
synchronized(objectYouNeedToLockOn){
    //set ready flag to true (so isReady returns true)
    ready = true;
    objectYouNeedToLockOn.notifyAll();
}
//end thread run method here

El objectYouNeedToLockOnque estoy usando en estos ejemplos es preferiblemente el objeto que necesita manipular simultáneamente de cada hilo, o podría crear uno separado Objectpara ese propósito (no recomendaría sincronizar los métodos):

private final Object lock = new Object();
//now use lock in your synchronized blocks

Para mejorar su comprensión:
hay otras formas (a veces mejores) de hacer lo anterior, por ejemplo, con CountdownLatches, etc. Desde Java 5 hay muchas clases ingeniosas de concurrencia en el java.util.concurrentpaquete y subpaquetes. Realmente necesita encontrar material en línea para conocer la concurrencia u obtener un buen libro.


Ni todo el código de hilo puede integrarse bien en los objetos, si no me equivoco. Por lo tanto, no creo que usar la sincronización de objetos sea una buena manera de implementar este trabajo relacionado con el hilo.
user1914692

@ user1914692: No estoy seguro de qué inconvenientes existen al usar el enfoque anterior. ¿Puede explicarlo más?
Piskvor salió del edificio

1
@Piskvor: Lo siento, escribí esto hace mucho tiempo y casi olvido lo que tenía en mente. Tal vez solo quise decir mejor usar el bloqueo, en lugar de la sincronización de objetos, siendo esta última una forma simplificada de la primera.
user1914692

No entiendo cómo funciona esto. Si el hilo aestá esperando en el objeto, synchronised(object)¿cómo puede pasar otro hilo synchronized(object)para llamar al object.notifyAll()? En mi programa, todo se atascó en synchronozedbloques.
Tomáš Zato - Restablece a Mónica el

@ TomášZato el primer hilo llama object.wait()efectivamente desbloqueando el bloqueo en ese objeto. Cuando el segundo subproceso "sale" de su bloque sincronizado, los otros objetos se liberan del waitmétodo y vuelven a obtener el bloqueo en ese punto.
rogerdpack

140

Use un CountDownLatch con un contador de 1.

CountDownLatch latch = new CountDownLatch(1);

Ahora en el hilo de la aplicación do-

latch.await();

En el hilo db, después de que haya terminado, haga:

latch.countDown();

44
Realmente me gusta esa solución por su simplicidad, aunque podría ser más difícil entender el significado del código a primera vista.
guitarra letal

3
Este uso requiere rehacer el pestillo cuando se agoten. Para obtener un uso similar a un evento Waitable en Windows, debe probar un BooleanLatch o un CountDownLatch reiniciable : docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/… stackoverflow.com/questions / 6595835 / ...
phyatt

1
Hola, si primero llamo a un método asíncrono que se supone que dispara un evento como tal: 1) asyncFunc (); 2) latch.await (); Luego hago una cuenta regresiva en la función de manejo de eventos una vez que se recibe. ¿Cómo puedo asegurarme de que el evento no se manejará ANTES de llamar a latch.await ()? Me gustaría evitar la preferencia entre las líneas 1 y 2. Gracias.
NioX5199

1
Para evitar esperar para siempre en caso de errores, poner countDown()en un finally{}bloque
Daniel Alder

23

Requisito ::

  1. Para esperar la ejecución del siguiente subproceso hasta que finalice el anterior.
  2. El siguiente subproceso no debe comenzar hasta que se detenga el subproceso anterior, independientemente del consumo de tiempo.
  3. Debe ser simple y fácil de usar.

Responder ::

@Ver java.util.concurrent.Future.get () doc.

future.get () Espera si es necesario para que se complete el cálculo y luego recupera su resultado.

¡¡Trabajo hecho!! Ver ejemplo a continuación

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.junit.Test;

public class ThreadTest {

    public void print(String m) {
        System.out.println(m);
    }

    public class One implements Callable<Integer> {

        public Integer call() throws Exception {
            print("One...");
            Thread.sleep(6000);
            print("One!!");
            return 100;
        }
    }

    public class Two implements Callable<String> {

        public String call() throws Exception {
            print("Two...");
            Thread.sleep(1000);
            print("Two!!");
            return "Done";
        }
    }

    public class Three implements Callable<Boolean> {

        public Boolean call() throws Exception {
            print("Three...");
            Thread.sleep(2000);
            print("Three!!");
            return true;
        }
    }

    /**
     * @See java.util.concurrent.Future.get() doc
     *      <p>
     *      Waits if necessary for the computation to complete, and then
     *      retrieves its result.
     */
    @Test
    public void poolRun() throws InterruptedException, ExecutionException {
        int n = 3;
        // Build a fixed number of thread pool
        ExecutorService pool = Executors.newFixedThreadPool(n);
        // Wait until One finishes it's task.
        pool.submit(new One()).get();
        // Wait until Two finishes it's task.
        pool.submit(new Two()).get();
        // Wait until Three finishes it's task.
        pool.submit(new Three()).get();
        pool.shutdown();
    }
}

Salida de este programa ::

One...
One!!
Two...
Two!!
Three...
Three!!

Puede ver que tarda 6 segundos antes de terminar su tarea, que es mayor que otro hilo. Así que Future.get () espera hasta que se complete la tarea.

Si no usa future.get () no espera para terminar y ejecuta el consumo de tiempo basado.

Buena suerte con la concurrencia de Java.


¡Gracias por su respuesta! He usado CountdownLatches, pero el tuyo es un enfoque mucho más flexible.
Piskvor salió del edificio el

9

Muchas respuestas correctas pero sin un ejemplo simple. Aquí hay una manera fácil y simple de usar CountDownLatch:

//inside your currentThread.. lets call it Thread_Main
//1
final CountDownLatch latch = new CountDownLatch(1);

//2
// launch thread#2
new Thread(new Runnable() {
    @Override
    public void run() {
        //4
        //do your logic here in thread#2

        //then release the lock
        //5
        latch.countDown();
    }
}).start();

try {
    //3 this method will block the thread of latch untill its released later from thread#2
    latch.await();
} catch (InterruptedException e) {
    e.printStackTrace();
}

//6
// You reach here after  latch.countDown() is called from thread#2

8
public class ThreadEvent {

    private final Object lock = new Object();

    public void signal() {
        synchronized (lock) {
            lock.notify();
        }
    }

    public void await() throws InterruptedException {
        synchronized (lock) {
            lock.wait();
        }
    }
}

Use esta clase como esta entonces:

Crea un ThreadEvent:

ThreadEvent resultsReady = new ThreadEvent();

En el método esto está esperando resultados:

resultsReady.await();

Y en el método que está creando los resultados después de haber creado todos los resultados:

resultsReady.signal();

EDITAR:

(Perdón por editar esta publicación, pero este código tiene una condición de carrera muy mala y no tengo suficiente reputación para comentar)

Solo puede usar esto si está 100% seguro de que se llama a signal () después de wait (). Esta es la gran razón por la que no puede utilizar objetos Java como, por ejemplo, Windows Events.

Si el código se ejecuta en este orden:

Thread 1: resultsReady.signal();
Thread 2: resultsReady.await();

entonces el hilo 2 esperará para siempre . Esto se debe a que Object.notify () solo activa uno de los subprocesos actualmente en ejecución. Un hilo que espera más tarde no se despierta. Esto es muy diferente de cómo espero que funcionen los eventos, donde se señala un evento hasta que a) espere ob) restablezca explícitamente.

Nota: La mayoría de las veces, debe usar notifyAll (), pero esto no es relevante para el problema de "esperar para siempre" anterior.


7

Pruebe la clase CountDownLatch fuera del java.util.concurrentpaquete, que proporciona mecanismos de sincronización de mayor nivel, que son mucho menos propensos a errores que cualquiera de las cosas de bajo nivel.


6

Puede hacerlo utilizando un objeto Intercambiador compartido entre los dos hilos:

private Exchanger<String> myDataExchanger = new Exchanger<String>();

// Wait for thread's output
String data;
try {
  data = myDataExchanger.exchange("");
} catch (InterruptedException e1) {
  // Handle Exceptions
}

Y en el segundo hilo:

try {
    myDataExchanger.exchange(data)
} catch (InterruptedException e) {

}

Como han dicho otros, no tome este código alegre y solo copie y pegue. Lee un poco primero.


4

La interfaz Future del java.lang.concurrentpaquete está diseñada para proporcionar acceso a los resultados calculados en otro hilo.

Eche un vistazo a FutureTask y ExecutorService para obtener una forma lista de hacer este tipo de cosas.

Recomiendo leer Java Concurrency In Practice a cualquier persona interesada en la concurrencia y el subprocesamiento múltiple. Obviamente se concentra en Java, pero también hay mucha carne para cualquiera que trabaje en otros idiomas.


2

Si desea algo rápido y sucio, puede agregar una llamada Thread.sleep () dentro de su ciclo while. Si la biblioteca de la base de datos es algo que no puede cambiar, entonces realmente no hay otra solución fácil. El sondeo de la base de datos hasta que esté listo con un período de espera no matará el rendimiento.

while (!dbthread.isReady()) {
  Thread.sleep(250);
}

Difícilmente algo que se pueda llamar código elegante, pero hace el trabajo.

En caso de que pueda modificar el código de la base de datos, es mejor usar un mutex como se propone en otras respuestas.


3
Esto es bastante ocupado esperando. El uso de construcciones de los paquetes util.concurrent de Java 5 debería ser el camino a seguir. stackoverflow.com/questions/289434/… me parece la mejor solución por ahora.
Cem Catikkas

Está ocupado esperando, pero si solo es necesario en este lugar en particular, y si no hay acceso a la biblioteca db, ¿qué más puede hacer? La espera ocupada no es necesariamente malvada
Mario Ortegón

2

Esto se aplica a todos los idiomas:

Desea tener un modelo de evento / oyente. Crea un oyente para esperar un evento en particular. El evento se crearía (o se señalaría) en su hilo de trabajo. Esto bloqueará el hilo hasta que se reciba la señal en lugar de sondear constantemente para ver si se cumple una condición, como la solución que tiene actualmente.

Su situación es una de las causas más comunes de puntos muertos: asegúrese de señalar el otro hilo independientemente de los errores que puedan haber ocurrido. Ejemplo, si su aplicación arroja una excepción, y nunca llama al método para indicarle al otro que las cosas se han completado. Esto hará que el otro hilo nunca 'se despierte'.

Le sugiero que examine los conceptos de uso de eventos y controladores de eventos para comprender mejor este paradigma antes de implementar su caso.

Alternativamente, puede usar una llamada de función de bloqueo usando un mutex, lo que hará que el hilo espere a que el recurso esté libre. Para hacer esto, necesita una buena sincronización de hilos, como:

Thread-A Locks lock-a
Run thread-B
Thread-B waits for lock-a
Thread-A unlocks lock-a (causing Thread-B to continue)
Thread-A waits for lock-b 
Thread-B completes and unlocks lock-b

2

Puede leer desde una cola de bloqueo en un hilo y escribir en otro hilo.


1

Ya que

  1. join() ha sido descartado
  2. ya has usado CountDownLatch y
  3. Future.get () ya ha sido propuesto por otros expertos,

Puedes considerar otras alternativas:

  1. invokeAll fromExecutorService

    invokeAll(Collection<? extends Callable<T>> tasks)

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

  2. ForkJoinPool o newWorkStealingPool de Executors(desde el lanzamiento de Java 8)

    Crea un grupo de subprocesos de robo de trabajo utilizando todos los procesadores disponibles como su nivel de paralelismo objetivo.


-1

ingrese la descripción de la imagen aquí

Esta idea puede aplicarse ?. Si usa CountdownLatches o Semaphores funciona perfecto, pero si está buscando la respuesta más fácil para una entrevista, creo que esto puede aplicarse.


1
¿Cómo está esto bien para una entrevista pero no para el código real?
Piskvor salió del edificio

Porque en este caso se está ejecutando secuencialmente, espere a otro. La mejor solución podría ser usar semáforos porque usar CountdownLatches es la mejor respuesta dada aquí, el Thread nunca se duerme, lo que significa el uso de ciclos de CPU.
Franco

Pero el punto no era "ejecutarlos secuencialmente". Editaré la pregunta para aclarar esto: el hilo GUI espera hasta que la base de datos esté lista, y luego ambos se ejecutan simultáneamente durante el resto de la ejecución de la aplicación: el hilo GUI envía comandos al hilo DB y lee los resultados. (Y de nuevo: ¿cuál es el punto del código que se puede usar en una entrevista pero no en el código real? La mayoría de los entrevistadores técnicos que he conocido tenían antecedentes en el código y harían la misma pregunta; además, necesitaba esto para una aplicación real Estaba escribiendo en ese momento, no por pasar la tarea)
Piskvor salió del edificio

1
Entonces. Este es un problema del consumidor productor que utiliza semáforos. Intentaré hacer un ejemplo
Franco

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.