2012-05-26 18 views
5

Używam CountDownLatch do oczekiwania na określone zdarzenie z innego składnika (działa w innym wątku). Następujące podejście pasowałoby semantykę mojego oprogramowania, ale nie jestem pewien, czy to działa jak oczekuję:Wiele wywołań do CountDownLatch.await (int) z limitem czasu

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

Scenariusz powinien być następujący: Czekam na 3 sekundy, a jeśli jest zatrzask nie licząc do 0, powiadamiam drugi komponent o tej zmiennej, a następnie czekam najwyżej 3,5 sekundy. Jeśli pojawi się czas oczekiwania ponownie, nie obchodzi mnie to i będę kontynuował inne operacje.

Uwaga: Wiem, że tak nie wygląda, ale powyższy scenariusz jest całkowicie uzasadniony i ważny w moim oprogramowaniu.

Przeczytałem dokumentację oczekujących (int, TimeUnit) i CountDownLatch, ale nie jestem ekspertem od Java/Androida, więc potrzebuję potwierdzenia. Dla mnie wszystkie scenariusze wyglądać prawidłowy:

  • Jeśli pierwszy Oczekujcie powiedzie się, wówczas drugi czekają powróci natychmiast
  • Jeśli pierwsze czasy czekają out, potem drugą Oczekujcie nadal jest ważny; zatem, jeśli druga nitka zauważy sygnału statycznego, drugi Oczekujcie może powrócić powodzeniem
  • Obaj czekają na rozmowy time out (to jest w porządku, zgodnie z semantyką moje oprogramowanie jest)

używam Oczekujcie (.. .) poprawnie? Czy drugi może czekać (...) na powyższy sposób, nawet jeśli wcześniej oczekiwano (...) na ten sam obiekt?

Odpowiedz

4

Jeśli dobrze rozumiem twoje pytanie, ten test udowodni, że wszystkie twoje założenia/wymagania są prawdziwe/spełnione. (Uruchom z JUnit i Hamcrest.) Uwaga kodu w metodzie runCodeUnderTest(), choć to przeplatane rejestracji czasu i limity czasu są redukowane przez współczynnik 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

Dzięki za bardzo dokładnego kodu. Właściwie moje pytanie było znacznie prostsze, ale jeśli mam rację, faktycznie * udowodniono, że moje założenia są poprawne, więc wielkie dzięki za to! Moje główne pytanie było tylko to: jeśli pierwszy oczekuj() ** razy out ** dla obiektu, czy mogę ponownie wywołać funkcję wait() na tym samym obiekcie (jak w moim kodzie)? Przypuszczam, że tak, ale jak już powiedziałem, nie jestem ekspertem od Javy. (Po otrzymaniu ostatecznego potwierdzenia, na pewno zaznaczę twoją odpowiedź jako odpowiedź, ponieważ jest to więcej niż to, czego potrzebowałem.) –

+1

Heh, cóż, szczerze mówiąc, nie byłem w 100% pewny odpowiedzi z góry moja głowa, a najlepszym sposobem znalezienia takiej odpowiedzi jest wypróbowanie jej. Stąd test :) –

Powiązane problemy