【问题标题】:Multiple calls to CountDownLatch.await(int) with timeout多次调用 CountDownLatch.await(int) 超时
【发布时间】:2012-05-26 23:05:04
【问题描述】:

我使用 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(...)?

【问题讨论】:

    标签: java android synchronization countdownlatch


    【解决方案1】:

    如果我正确理解了您的问题,则此测试证明您的所有假设/要求都是正确/满足的。 (使用 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);
            }
        }
    }
    

    【讨论】:

    • 感谢您提供非常详尽的代码。实际上,我的问题比这简单得多,但如果我是正确的,您实际上证明我的假设是正确的,非常感谢!我的核心问题只是:如果第一个 await() times out 对于一个对象,我可以在同一个对象上再次调用 await() 吗(就像在我的代码中一样)?我想是的,但正如我所说,我不是 Java 专家。 (在我得到最终确认后,我一定会把你的回复标记为答案,因为它比我需要的要多。)
    • 嘿,老实说,我并不能 100% 确定答案,而找到这样的答案的最佳方法就是尝试一下。因此测试:)
    • 在使用倒计时锁存器时,我们需要考虑两件事——
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多