2015-06-04 1 views
2

Я только начал читать код JUnit 4.13 (https://github.com/junit-team/junit), и получить немного запутался об осуществлении org.junit.internal.runners.statements.FailOnTimeout:Почему JUnit с помощью CountDownLatch реализовать 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() раз метод испытания.

Вот мой вопрос:

  • Почему threadevaluate()) должен быть демон нить?
  • Действительно ли CountDownLatch используется только для блокировки getResult() до thread? Действительно ли это работает (я думал, что ничто не может предотвратить переключение контекста между callable.awaitStarted() и getResult())? Есть ли «более простой» способ сделать это?

Я не очень знаком с параллелизмом. И я был бы очень признателен, если кто-нибудь сможет объяснить это или указать мои ошибки. Благодарю.


Больше объяснения по поводу второго вопроса:

Я бы обозначить две темы, как «оценка() нить» и «CallableStatement нить».

Я думаю, что «Оценить() поток» заблокирован, когда callable.awaitStarted() выполняется до тех пор, пока не будет выполнено startLatch.countDown(), но метод тестирования может начать работать до того, как контекст переключится на «оценить() поток». Сразу после того, как «review() thread« снова пробуждается », он вызывает FutureTask.get(), который блокирует« поток()() », но не может гарантировать, что« CallableStatement thread »будет пробужден сразу после этого.

Итак, я думаю, что момент начала тестового метода не имеет ничего общего с моментом task.get(timeout, timeUnit). Если есть много других потоков, между ними может быть нерегулярный временной интервал.

ответ

4

Почему thread (in evaluation()) должен быть потоком демона?

Если что-то не так с тестом, это позволяет JVM выйти из строя. См. Также What is Daemon thread in Java?

Is CountDownLatch используется только для блокировки getResult() до тех пор, пока поток не будет запущен? Это действительно работает (я думал, что ничто не может предотвратить переключение контекста между callable.awaitStarted() и getResult())? Есть ли «более простой» способ сделать это?

Ничто не может предотвратить переключение контекста, но если thread уже запущен и работает он получит в конечном счете некоторое внимание CPU и originalStatement.evaluate() будет выполняться.Причина, по которой это делается, заключается в том, что не может быть базового потока операционной системы, и тогда тестовое выполнение теста может потерпеть неудачу, хотя сам тест является правильным. Существуют также другие способы, как это сделать, например, Semaphore, но CountDownLatch довольно эффективный и дешевый примитивный для этого.

+0

Большое спасибо за ваш совет, теперь я понимаю поток Daemon. По второму вопросу, я отредактировал свой пост с еще одним объяснением, надеюсь, что вы могли бы ответить на это. Еще раз спасибо. – Morrissss

+0

Вы частично правы: «Если есть много других потоков, между ними может быть нерегулярный промежуток времени». таким образом, что может потребоваться некоторое время для переключения контекстов, но на практике, если в потоке Runnable не слишком много потоков, и большинство потоков имеют одинаковый приоритет, это должен быть незначительный временной интервал. Также я бы предположил, что «тайм-аут» не предназначен как инструмент для измерения времени, а скорее как безопасность для неправильной работы потоков. – JiriS

+0

'FailOnTimeout' используется для проверки того, может ли тестовый метод завершиться в' timeout'. Поэтому «timeout» используется для измерения времени. Я думаю, что 'callable.awaitStarted()' не может помешать запуску тестового метода, а 'task.get()' не может предотвратить пробуждение других потоков, кроме потока CallableStatement. Поэтому 'CountDownLatch' не работает лучше, чем просто используя' FutureTask', и желая, чтобы это было пробуждено как можно скорее. – Morrissss

Смежные вопросы