5

根据清单 12.3 中的Java Concurrency in Practice一书,我们可以使用以下示例代码测试并发代码:

void testTakeBlocksWhenEmpty() {
 final BoundedBuffer<Integer> bb = new BoundedBuffer<Integer>(10);
 Thread taker = new Thread() {
  public void run() {
   try {
    int unused = bb.take();
    fail(); // if we get here, it’s an error
   } catch (InterruptedException success) { }
  }
 };
 try {
  taker.start();
  Thread.sleep(LOCKUP_DETECT_TIMEOUT);
  taker.interrupt();
  taker.join(LOCKUP_DETECT_TIMEOUT);
  assertFalse(taker.isAlive());
 } catch (Exception unexpected) {
  fail();
 }
}

假设执行了以下步骤:

  1. taker线程开始了。
  2. bb.take()fail()成功返回,我们只是在方法运行之前一点点。
  3. 它被称为interrupt()方法。
  4. 我们在线程的catch块。taker

所以,我们现在处于 catch 块,但实际上测试方法失败了。它失败了,我们永远不会被告知。

这是正确的吗?如果是,我们如何解决这个问题?

4

2 回答 2

1

测试线程/异步代码的一般方法是阻塞主测试线程,从其他线程捕获失败的断言,重新启动主测试线程并重新抛出任何失败。手工完成是相当样板的,这是像ConcurrentUnit这样的库很方便的地方。前任:

  final Waiter waiter = new Waiter();

  new Thread(() -> {
    doSomeWork();
    waiter.assertTrue(true);
    waiter.resume();
  }).start();

  // Wait for resume() to be called
  waiter.await(1000);
于 2015-07-24T04:36:14.350 回答
0

take应该在一个空队列上阻塞。所以预期的事件顺序是:

  • taker.start();=> 启动线程
  • Thread.sleep(LOCKUP_DETECT_TIMEOUT);等待以确保线程已启动并take已被调用。常量的实际值很难估计,但任何高于几百毫秒的值就足够了 - 或者,您可以使用 CountDownLatch 来了解接收线程何时启动
  • 在接受者线程中:bb.take();=> 应该阻塞 - 如果它没有fail()被调用并且测试失败
  • 在主线程中:taker.interrupt();=>该take()方法应该退出InterruptedException
  • 在主线程中:taker.join();=> 等待一段时间以允许接受者线程完成
  • 在主线程中:assertFalse(taker.isAlive());=> 确认接受者线程已经退出并且不再被take方法阻塞

带有锁存器的版本(它假设如果线程在调用之前 被中断,则会以 InterruptedException 退出 - 如果没有,那么您别无他法,只能在调用之前添加一些随机睡眠):taketakestarted.await()

void testTakeBlocksWhenEmpty() {
 final CountDownLatch started = new CountDownLatch(1);
 final CountDownLatch ended = new CountDownLatch(1);
 final BoundedBuffer<Integer> bb = new BoundedBuffer<Integer>(10);
 Thread taker = new Thread() {
  public void run() {
   try {
    started.countDown();
    int unused = bb.take();
    fail(); // if we get here, it’s an error
   } catch (InterruptedException success) { }
   ended.countDown();
  }
 };
 try {
  taker.start();
  started.await();
  taker.interrupt();
  assertTrue(ended.await());
 } catch (Exception unexpected) {
  fail();
 }
}

您应该为您的测试方法或锁存器添加一个超时时间(如果测试通过,则足够长,不会干扰,例如 5 秒)。这将避免阻塞您的整个测试套件。

于 2013-10-02T08:43:07.217 回答