【问题标题】:Why JUnit using CountDownLatch to implement FailOnTimeout为什么 JUnit 使用 CountDownLatch 来实现 FailOnTimeout
【发布时间】:2015-06-04 08:02:38
【问题描述】:

我刚开始阅读 JUnit 4.13 (https://github.com/junit-team/junit) 的代码,对 org.junit.internal.runners.statements.FailOnTimeout 的实现有点困惑:

@Override
public void evaluate() throws Throwable {
    CallableStatement callable = new CallableStatement();
    FutureTask<Throwable> task = new FutureTask<Throwable>(callable);
    ThreadGroup threadGroup = new ThreadGroup("FailOnTimeoutGroup");
    Thread thread = new Thread(threadGroup, task, "Time-limited test");
    thread.setDaemon(true);
    thread.start();
    callable.awaitStarted();
    Throwable throwable = getResult(task, thread);
    if (throwable != null) {
        throw throwable;
    }
}

/**
 * Wait for the test task, returning the exception thrown by the test if the
 * test failed, an exception indicating a timeout if the test timed out, or
 * {@code null} if the test passed.
 */
private Throwable getResult(FutureTask<Throwable> task, Thread thread) {
    try {
        if (timeout > 0) {
            return task.get(timeout, timeUnit);  // HERE limits the time
        } else {
            return task.get();
        }
    } catch (InterruptedException e) {
        return e; // caller will re-throw; no need to call Thread.interrupt()
    } catch (ExecutionException e) {
        // test failed; have caller re-throw the exception thrown by the test
        return e.getCause();
    } catch (TimeoutException e) {
        return createTimeoutException(thread);
    }
}

CallableStatement 在哪里:

private class CallableStatement implements Callable<Throwable> {
    private final CountDownLatch startLatch = new CountDownLatch(1);

    public Throwable call() throws Exception {
        try {
            startLatch.countDown();
            originalStatement.evaluate();  // HERE the test runs
        } catch (Exception e) {
            throw e;
        } catch (Throwable e) {
            return e;
        }
        return null;
    }

    public void awaitStarted() throws InterruptedException {
        startLatch.await();
    }
}

以下是我对代码的理解:

evaluate() 为测试方法启动一个新线程。 callable.awaitStarted() 阻止 evaluate() 直到 startLatch.countDown(),然后 getResult() 乘以测试方法。

这是我的问题:

  • 为什么thread(在evaluate())应该是一个守护线程?
  • CountDownLatch 是否仅用于阻止getResult() 直到thread 运行?它真的有效吗(我认为没有什么可以避免callable.awaitStarted()getResult() 之间的上下文切换)?有没有“更简单”的方法来做到这一点?

我对并发不是很熟悉。如果有人能解释这些或指出我的错误,我将不胜感激。谢谢。


关于第二个问题的更多解释:

我将这两个线程分别表示为“evaluate() 线程”和“CallableStatement 线程”。

我认为当callable.awaitStarted() 执行到startLatch.countDown() 完成时,“evaluate() 线程”会被阻塞,但测试方法可能会在上下文切换回“evaluate() 线程”之前开始运行。在“evaluate()线程”再次被唤醒后,它调用FutureTask.get(),这将阻塞“evaluate()线程”,但不能保证“CallableStatement线程”在此之后立即被唤醒。

所以,我认为测试方法开始的那一刻与task.get(timeout, timeUnit) 被调用的那一刻无关。如果有很多其他线程,它们之间的时间间隔可以忽略不计。

【问题讨论】:

    标签: java junit concurrency


    【解决方案1】:

    为什么线程(在评估()中)应该是一个守护线程?

    如果测试有任何问题,它允许 JVM 正确退出。另见What is Daemon thread in Java?

    CountDownLatch 是否仅用于阻塞 getResult() 直到线程运行?它真的有效吗(我认为没有什么可以避免 callable.awaitStarted() 和 getResult() 之间的上下文切换)?有没有“更简单”的方法来做到这一点?

    没有什么可以避免上下文切换,但如果thread 已经启动并运行,它最终会引起CPU 的注意,originalStatement.evaluate() 将被执行。这样做的原因是可能没有可用的底层操作系统线程,然后测试的测试执行可能会失败,尽管测试本身是正确的。还有其他方法可以做到这一点,例如Semaphore,但 CountDownLatch 非常高效,而且 便宜 很原始。

    【讨论】:

    • 非常感谢您的建议,现在我了解了 Daemon 线程。对于第二个问题,我已经对我的帖子进行了更多解释,希望您能回答。再次感谢。
    • “如果有很多其他线程,它们之间的时间间隔可能不可忽略。”你部分正确。以某种方式切换上下文可能需要一些时间,但实际上如果没有太多线程处于可运行状态并且大多数线程具有相同的优先级,则时间间隔应该可以忽略不计。我也猜想timeout 并不是用来测量时间的工具,而是作为行为不端线程的安全措施。
    • FailOnTimeout 用于测试一个测试方法是否可以在timeout 内完成。所以timeout用于时间测量。我认为callable.awaitStarted() 不能阻止测试方法启动,task.get() 不能阻止除“CallableStatement 线程”之外的线程在它之前被唤醒。所以CountDownLatch 并没有比简单地使用FutureTask 并希望它尽快被唤醒更好。
    • 应该写出精确的时间测量。我的猜测是,在正常情况下,最多不会有超过一(或几)毫秒(或者可能是数百纳秒)的差异,但没有尝试过。一般来说,与不使用CountDownLatch 时的差异相比,差异要小得多,因为线程初始化可能需要while
    • 我参与了编写和审查对FailOnTimeout 的更改,@JiriS 是正确的。在某些环境中,线程启动时间可能很重要且可变。等待锁存器允许FailOnTimeout 在包装的Statement 开始之前不会“启动时钟”。是的,这意味着如果运行时间刚刚超过指定限制,您的测试有时可能会通过,但我们认为这是可以接受的。
    猜你喜欢
    • 1970-01-01
    • 2023-03-21
    • 2011-01-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-28
    相关资源
    最近更新 更多