Java 为什么JUnit使用CountDownLatch实现FailOnTimeout
我刚开始阅读JUnit4.13()的代码,对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
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();
}
}