6

I use a CountDownLatch for waiting for a certain event from another component (running in a different thread). The following approach would fit the semantics of my software, but I'm not sure whether it works as a I expect:

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

The scenario should be the following: I wait for 3 seconds, and if the latch is not counted down to 0, I notify the other component with that variable, and then I wait at most 3.5 seconds. If there is timeout again, then I don't care and will proceed with other operations.

Note: I know it doesn't look like that, but the above scenario is totally reasonable and valid in my software.

I did read the documentation of await(int,TimeUnit) and CountDownLatch, but I'm not a Java/Android expert, so I need confirmation. To me, all scenarios look valid:

  • If the first await is successful, then the other await will return immediately
  • If the first await times out, then the other await is still valid; therefore, if the other thread notices the static signal,the second await might return successfully
  • Both await calls time out (this is fine according to my software's semantics)

Am I using await(...) correctly? Can a second await(...) be used in the above way even if a previous await(...) on the same object timed out?

Thomas Calc
  • 2,994
  • 3
  • 30
  • 56

1 Answers1

4

If I understand your question correctly, this test proves that all your assumptions/requirements are true/met. (Run with JUnit and Hamcrest.) Note your code in the runCodeUnderTest() method, though it's interspersed with time recording and the timeouts are reduced by a factor of 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);
        }
    }
}
Ryan Stewart
  • 126,015
  • 21
  • 180
  • 199
  • Thanks for the very thorough code. Actually, my question was much simpler than this, but if I'm correct, you actually *proved* that my assumptions are correct, so a big thanks for that! My core question was only this: if the first await() **times out** for an object, can I call await() again on the same object (as in my code)? I suppose yes, but as I said, I'm not a Java expert. (After I get the final confirmation, I'll certainly mark your reply as an answer, because it's more than what I needed.) – Thomas Calc May 27 '12 at 01:23
  • 1
    Heh, well to be honest, I wasn't 100% certain of the answer off the top of my head, and the best way to find an answer like that is to try it. Hence the test :) – Ryan Stewart May 27 '12 at 02:09
  • While using Countdown latch, we need to consider two things - – hi.nitish May 22 '18 at 10:54