2011-12-19 16 views
12

Tengo un hilo que actualiza su estado de vez en cuando y quiero que un segundo hilo pueda esperar a que se realice el primer hilo. Algo como esto:Esperando un evento en Java: ¿qué tan difícil es?

Thread 1: 
    while(true) { 
     ...do something... 
     foo.notifyAll() 
     ...wait for some condition that might never happen... 
     ... 
    } 

Thread 2: 
    ... 
    foo.wait(); 
    ... 

Ahora bien, esto se ve bien y todo a menos de rosca de 1 notifyAll() se ejecuta antes de espera de rosca de 2(), en la cual Tema caso 2 espera hasta Tema 1 notifica nuevo (que nunca podría suceder) .

Mis posibles soluciones:

a) que podría utilizar un CountDownLatch o un futuro, pero ambos tienen el problema de que por sí sólo se ejecutan una vez . Es decir, en el ciclo while de Thread 1, necesitaría crear un nuevo foo para esperar cada vez y el Thread 2 necesitaría preguntar a qué foo esperar. Tengo un mal presentimiento sobre simplemente escribiendo

while(true) { 
    foo = new FutureTask(); 
    ... 
    foo.set(...); 
    ...wait for a condition that might never be set... 
    ... 
} 

como me temo que al foo = nueva FutureTask(), lo que sucede cuando alguien esperó a que el viejo foo (por "alguna razón", estableció que no fue llamado, por ejemplo, una error en el manejo de excepciones)?

b) O podría usar un semáforo:

class Event { 
    Semaphore sem; 
    Event() { sem = new Semaphore(1); sem . } 
    void signal() { sem.release(); } 
    void reset() { sem.acquire(1); } 
    void wait() { if (sem.tryAcquire(1)) { sem.release(); } } 
} 

Pero me temo que hay una cierta condición de carrera, si varios subprocesos son wait() ing para ello, mientras que otra señal() s y reset () s.

Pregunta:

¿No hay nada en la API de Java que se asemeja al comportamiento de eventos de Windows? O, si desprecia Windows, algo así como el WaitGroup de Golang (es decir, un CountDownLatch que permite countUp())? ¿Cualquier cosa?

Como hacerlo manualmente:

Tema 2 no se puede esperar simplemente a causa de despertar y espuria en Java no hay manera de saber por qué regresó Object.wait(). Entonces necesito una variable de condición que almacene si el evento está señalado o no. Tema 2:

synchronized(foo) { 
    while(!condition) { 
     foo.wait(); 
    } 
} 

Y hilo 1, por supuesto, establece la condición de verdad en un bloque sincronizado. ¡Gracias a weekens por la pista!

¿Existe una clase existente que ajuste ese comportamiento?

¿Debo copiar y pegar el código?

+0

¿Qué problema en realidad tratando de resolver? Aunque estoy seguro de que este problema se puede resolver, aún así me gustaría preguntar, así sabremos que esta es la solución correcta para su problema. Este tipo de juego con los subprocesos y semáforos, aunque bien entendido, es falsamente propenso a fallas debido a pequeños errores, por lo que podría haber una forma más legible y fácil de entender para hacer lo que desea lograr. En particular, suena como un patrón simple de escucha uniforme donde los oyentes se registran con la fuente de los eventos sería más adecuado. – Thor84no

+0

Sí, un oyente de eventos resolvería el problema dejando que el hilo 1 se actualice. Este suele ser el método preferido, p. en un entorno de servidor. Sin embargo, me encontré con el problema de "extraer" hilo de forma segura para las actualizaciones de vez en cuando y nunca tuve una solución limpia hasta ahora. – Kosta

Respuesta

18

Es una práctica estándar cambiar algún estado al realizar notifyAll y verificar algún estado al ejecutar wait().

p. Ej.

boolean ready = false; 

// thread 1 
synchronized(lock) { 
    ready = true; 
    lock.notifyAll(); 
} 


// thread 2 
synchronized(lock) { 
    while(!ready) 
     lock.wait(); 
} 

Con este enfoque, no importa si el hilo o hebra 1 2 adquiere el bloqueo en primer lugar.

Algunas herramientas de análisis de codificación le darán una advertencia si usa notify o espera sin establecer un valor o verificar un valor.

+0

Sí, eso es todo. ¡Gracias! – Kosta

+2

¿Esto no lleva a una situación de punto muerto donde el Subproceso 2 agarra 'bloqueo' y no se liberará hasta que abandone el bloque (pero no se irá, porque está esperando que 'listo' sea verdadero, lo que nunca sucederá mientras que el hilo 2 tiene 'bloqueo')? – Thor84no

+3

'lock.wait()' libera el 'lock' y lo vuelve a adquirir antes de volver. –

2

La forma más sencilla es igual que decir

firstThread.join();

Esto se bloquea hasta que se termine el primer hilo.

Pero puede implementar el mismo usando wait/notify. Lamentablemente, no ha publicado los fragmentos de código reales, pero supongo que si la espera no finaliza cuando llama, notifique que sucede porque no puso ambos en el bloque synchronized. Preste atención a que el "argumento" del bloque synchronized debe ser el mismo para el par de espera/notificación.

+0

Lo sentimos, pero en este caso, el primer subproceso se ejecuta en un ciclo while indefinidamente, por lo que firstThread.join() simplemente se ejecuta para siempre. wait() no sale porque se llama después de notifyAll(). Los bloques sincronizados y wait()/notifyAll() tienen todos los mismos argumentos. – Kosta

3

Puede usar wait() con tiempo de espera, en cuyo caso no correrá el riesgo de esperar para siempre. También tenga en cuenta que wait() puede regresar incluso si no hubo notificación() en absoluto, por lo tanto, deberá ajustar su espera dentro de algún ciclo condicionado. Esa es la forma estándar de esperar en Java.

synchronized(syncObject) { 
    while(condition.isTrue()) { 
     syncObject.wait(WAIT_TIMEOUT); 
    } 
} 

(en su hilo de rosca 2)

Editar: Movido sincronizado fuera del bucle.

+0

El problema es que el subproceso 2 pierde el primer notifyAll(), un tiempo de espera no ayuda a eso. Pero: me perdí la activación espuria en mi pregunta, así que tengo que dar cuenta de esto también. La variable de condición es realmente lo que necesito, pero prefiero while (condition.isFalse()) – Kosta

+0

Sí, puedes usar isFalse(), por supuesto. También el ciclo while podría estar dentro de la sección sincronizada. Este fragmento de código es solo para mostrar la idea general. – weekens

+0

El problema con esto es que la condición puede volverse cierta después de que se comprueba, pero antes de que se adquiera el bloqueo. Esto significa que terminas esperando() sin ningún motivo. –

2

Utilizaría un BlockingQueue entre los dos hilos. El uso de wait y notify es así que hace 5 minutos;)

enum Event { 
    Event, 
    Stop; 
} 

BlockingQueue<Event> queue = new LinkedBlockingQueue<Event>(); 

// Thread 1 
try { 
    while(true) { 
    ...do something... 
    queue.put(Event.Event); 
    ...wait for some condition that might never happen... 
    ... 
    } 
} finally { 
    // Tell other thread we've finished. 
    queue.put(Event.Stop}; 
} 

// Thread 2 
... 
switch (queue.take()) { 
    case Event: 
     ... 
     break; 

    default: 
     ... 
     break; 
} 
+0

Esto funciona, pero lo que no me gusta es que el subproceso 1 debe saber cuántos subprocesos lo están esperando: si tiene n subprocesos que son take() ing, el subproceso 1 necesita poner n eventos – Kosta

+0

@Kosta - ¿podría ampliar ese requisito en su pregunta, por favor? Estoy seguro de que podemos proporcionar esa funcionalidad también. – OldCurmudgeon

+0

Creo que también le gustaría echarle un vistazo a CyclicBarrier: esto manejará todos los trabajos que haya nombrado y no requiere ningún código de bajo nivel también :) - http://docs.oracle.com/javase/6/docs/api/ java/util/concurrent/CyclicBarrier.html –

Cuestiones relacionadas