根据清单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();
 }
}

假设执行了以下步骤:
  • taker线程已启动。
  • bb.take()成功返回,距离fail()方法运行还有点距离。
  • 称为interrupt()方法。
  • 我们位于catch线程的taker块。

  • 因此,我们目前处于困境,但实际上测试方法失败了。它失败了,我们从不知道。

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

    最佳答案

    take应该在空队列中阻塞。因此,预期的事件顺序为:

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

  • 带有闩锁的版本(它假定如果线程在调用take之前被中断,则take将以InterruptedException退出-如果没有,则您别无选择,只能在调用started.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秒)。这样可以避免阻塞整个测试套件。

    08-18 16:58