6

我使用 CountDownLatch 来等待来自另一个组件的某个事件(在不同的线程中运行)。以下方法适合我的软件的语义,但我不确定它是否按我的预期工作:

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

场景应该是这样的:我等待3秒,如果latch没有倒数到0,我用那个变量通知其他组件,然后我最多等待3.5秒。如果再次超时,那么我不在乎,将继续进行其他操作。

注意:我知道它看起来不像,但上面的场景在我的软件中是完全合理和有效的。

我确实阅读了 await(int,TimeUnit) 和 CountDownLatch 的文档,但我不是 Java/Android 专家,所以我需要确认。对我来说,所有场景看起来都有效:

  • 如果第一个 await 成功,那么另一个 await 会立即返回
  • 如果第一个 await 超时,那么另一个 await 仍然有效;因此,如果其他线程注意到静态信号,第二个等待可能会成功返回
  • 两个等待调用都超时(根据我的软件语义这很好)

我使用 await(...) 正确吗?即使同一对象上的前一个 await(...) 超时,是否可以按上述方式使用第二个 await(...) ?

4

1 回答 1

4

如果我正确理解了您的问题,则此测试证明您的所有假设/要求都是正确/满足的。(使用 JUnit 和 Hamcrest 运行。)请注意runCodeUnderTest()方法中的代码,尽管它穿插了时间记录,并且超时减少了 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);
        }
    }
}
于 2012-05-26T23:34:36.677 回答