Java 为什么JUnit使用CountDownLatch实现FailOnTimeout

Java 为什么JUnit使用CountDownLatch实现FailOnTimeout,java,junit,concurrency,Java,Junit,Concurrency,我刚开始阅读JUnit4.13()的代码,对org.JUnit.internal.runners.statements.FailOnTimeout的实现有点困惑: @Override public void evaluate() throws Throwable { CallableStatement callable = new CallableStatement(); FutureTask<Throwable> task = new FutureTask<T

我刚开始阅读JUnit4.13()的代码,对
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);
    }
}
以下是我对代码的理解:

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

我的问题是:

  • 为什么
    thread
    (在
    evaluate()
    中)应该是守护进程线程
  • CountDownLatch
    是否仅用于阻止
    getResult()
    直到
    线程运行?它真的有效吗(我认为没有什么可以避免在
    callable.awaitStarted()
    getResult()
    之间进行上下文切换)?有什么“更简单”的方法可以做到这一点吗
我对并发性不是很熟悉。如果有人能解释这些或指出我的错误,我将不胜感激。谢谢


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

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

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

因此,我认为测试方法开始的那一刻与调用task.get(timeout,timeUnit)
的那一刻无关。如果有大量其他线程,它们之间可能存在不可忽略的时间间隔

为什么(evaluate()中的)线程应该是守护进程线程

如果测试有任何错误,它允许JVM正确退出。另见

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


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

非常感谢您的建议,现在我了解了守护进程线程。关于第二个问题,我已经编辑了我的文章并做了更多的解释,希望你能回答。再次感谢您。您对“如果有很多其他线程,它们之间可能存在不可忽略的时间间隔”的看法部分正确。在某种程度上,切换上下文可能需要一些时间,但实际上,如果没有太多处于可运行状态的线程,并且大多数线程具有相同的优先级,那么时间间隔应该可以忽略不计。我还想,
timeout
并不是用来测量时间的工具,而是用来保护行为不正常的线程。
FailOnTimeout
用于测试测试方法是否可以在
timeout
内完成。因此,
timeout
用于时间测量。我认为,
callable.awaitStarted()
不能阻止测试方法启动,
task.get()
不能阻止“CallableStatement线程”以外的线程在它之前被唤醒。因此,
CountDownLatch
并不比简单地使用
FutureTask
并希望它能尽快被唤醒要好。应该写下精确的时间测量。我的猜测是,在正常情况下不会有超过一(或几)毫秒(或数百纳秒)的差异,但还没有尝试过。一般来说,这种差异要比不使用
CountDownLatch
时的差异小得多,因为线程初始化可能需要一段时间。我参与了编写和审查对
FailOnTimeout
的更改,@JiriS是正确的。在某些环境中,线程启动时间可能是重要的且可变的。等待闩锁允许
FailOnTimeout
在包装的
语句启动之前不“启动时钟”。是的,这意味着如果运行时刚好超过指定的限制,您的测试有时可能会通过,但我们认为这是可以接受的。
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();
    }
}