2012-05-26 8 views
5

Uso CountDownLatch para esperar un determinado evento de otro componente (que se ejecuta en un hilo diferente). El siguiente enfoque cabría la semántica de mi software, pero no estoy seguro de si funciona como espero:Llamadas múltiples a CountDownLatch.await (int) con tiempo de espera

mCountDownLatch.await(3000, TimeUnit.MILLISECONDS) 
otherComponent.aStaticVolatileVariable = true; 
mCountDownLatch.await(3500, TimeUnit.MILLISECONDS); 
... <proceed with other stuff> 

El escenario debe ser el siguiente: espero a 3 segundos, y si la retención es Sin contar a 0, notifico al otro componente con esa variable y luego espero 3.5 segundos como máximo. Si hay tiempo de espera de nuevo, entonces no me importa y procederé con otras operaciones.

Nota: Sé que no se ve así, pero el escenario anterior es totalmente razonable y válido en mi software.

He leído la documentación de await (int, TimeUnit) y CountDownLatch, pero no soy un experto en Java/Android, por lo que necesito una confirmación. Para mí, todos los escenarios se ven válida:

  • Si el primer Await tiene éxito, entonces el otro se esperan volver inmediatamente
  • Si los primeros tiempos aguardan fuera, entonces el otro lo esperan sigue siendo válida; lo tanto, si el otro hilo se da cuenta de la señal estática, la segunda aguardan puede devolver correctamente
  • Ambos esperan tiempo de espera de llamadas (esto está muy bien de acuerdo a la semántica de mi software)

estoy usando lo esperan (.. .) correctamente? ¿Puede un segundo esperar (...) ser utilizado de la manera anterior incluso si se agotó el tiempo esperado (...) en el mismo objeto?

Respuesta

4

Si entiendo su pregunta correctamente, esta prueba demuestra que todas sus suposiciones/requisitos son verdaderas/cumplidas. (Corre con JUnit y Hamcrest.) Tenga en cuenta que su código en el método runCodeUnderTest(), aunque se entremezcla con el registro del tiempo y los tiempos de espera se reducen en un factor de 10.

import org.junit.Before; 
import org.junit.Test; 

import java.util.concurrent.CountDownLatch; 
import java.util.concurrent.TimeUnit; 

import static org.hamcrest.Matchers.closeTo; 
import static org.hamcrest.Matchers.lessThan; 
import static org.junit.Assert.assertThat; 

public class CountdownLatchTest { 
    static volatile boolean signal; 
    CountDownLatch latch = new CountDownLatch(1); 
    long elapsedTime; 
    long[] wakeupTimes = new long[2]; 

    @Before 
    public void setUp() throws Exception { 
     signal = false; 
    } 

    @Test 
    public void successfulCountDownDuringFirstAwait() throws Exception { 
     countDownAfter(150); 
     runCodeUnderTest(); 
     assertThat((double) elapsedTime, closeTo(150, 10)); 
     assertThat(wakeupTimeSeparation(), lessThan(10)); 
    } 

    @Test 
    public void successfulCountDownDuringSecondAwait() throws Exception { 
     countDownAfter(450); 
     runCodeUnderTest(); 
     assertThat((double) elapsedTime, closeTo(450, 10)); 
     assertThat((double) wakeupTimeSeparation(), closeTo(150, 10)); 
    } 

    @Test 
    public void neverCountDown() throws Exception { 
     runCodeUnderTest(); 
     assertThat((double) elapsedTime, closeTo(650, 10)); 
     assertThat((double) wakeupTimeSeparation(), closeTo(350, 10)); 
    } 

    @Test 
    public void countDownAfterSecondTimeout() throws Exception { 
     countDownAfter(1000); 
     runCodeUnderTest(); 
     assertThat((double) elapsedTime, closeTo(650, 10)); 
     assertThat((double) wakeupTimeSeparation(), closeTo(350, 10)); 
    } 

    @Test 
    public void successfulCountDownFromSignalField() throws Exception { 
     countDownAfterSignal(); 
     runCodeUnderTest(); 
     assertThat((double) elapsedTime, closeTo(300, 10)); 
    } 

    private int wakeupTimeSeparation() { 
     return (int) (wakeupTimes[1] - wakeupTimes[0]); 
    } 

    private void runCodeUnderTest() throws InterruptedException { 
     long start = System.currentTimeMillis(); 
     latch.await(300, TimeUnit.MILLISECONDS); 
     wakeupTimes[0] = System.currentTimeMillis(); 
     signal = true; 
     latch.await(350, TimeUnit.MILLISECONDS); 
     wakeupTimes[1] = System.currentTimeMillis(); 
     elapsedTime = wakeupTimes[1] - start; 
    } 

    private void countDownAfter(final long millis) throws InterruptedException { 
     new Thread(new Runnable() { 
      @Override 
      public void run() { 
       sleep(millis); 
       latch.countDown(); 
      } 
     }).start(); 
    } 

    private void countDownAfterSignal() { 
     new Thread(new Runnable() { 
      @Override 
      public void run() { 
       boolean trying = true; 
       while (trying) { 
        if (signal) { 
         latch.countDown(); 
         trying = false; 
        } 
        sleep(5); 
       } 
      } 
     }).start(); 
    } 

    private void sleep(long millis) { 
     try { 
      Thread.sleep(millis); 
     } catch (InterruptedException e) { 
      throw new IllegalStateException("Unexpected interrupt", e); 
     } 
    } 
} 
+0

Gracias por el código muy completo. En realidad, mi pregunta fue mucho más simple que esto, pero si estoy en lo cierto, realmente * demuestras * que mis suposiciones son correctas, ¡así que muchas gracias por eso! Mi pregunta principal era solo esta: si el primero espera (** ** agota **) un objeto, ¿puedo volver a llamar a await() en el mismo objeto (como en mi código)? Supongo que sí, pero como dije, no soy un experto en Java. (Después de recibir la confirmación final, ciertamente marcaré su respuesta como respuesta, porque es más de lo que necesitaba). –

+1

Heh, para ser sincero, no estaba 100% seguro de la respuesta de la parte superior de mi cabeza, y la mejor manera de encontrar una respuesta como esa es intentarlo. De ahí la prueba :) –

Cuestiones relacionadas