并行流中的 Mockito when() 意外结果

Mockito when() in parallel stream unexpected result

我在我们的一项测试中调查 Mockito 的行为,但我不理解它的行为。以下代码片段显示了相同的行为:

@Test
public void test(){
    var mymock = Mockito.mock(Object.class);
    var l = List.of(1,2,3);
    l.parallelStream().forEach(val -> {
        int finalI = val;
        doAnswer(invocationOnMock -> {
            log.info("{}",finalI);
            return null;
        }).when(mymock).toString();
        mymock.toString();
    });
}

我期待的输出是按某种顺序打印 1、2、3(不是 nessaseraly 排序的):

我收到的输出:

2
2
2

为什么我得到这个输出?

这是一个典型的竞争条件:您的并行流并行执行 forEach lambda 三次。在这种情况下,所有三个线程都设法执行了 doAnswer() 调用,然后它们中的任何一个都到达了 mymock.toString() 行。 val=2 的执行恰好发生在 运行 最后。令人高兴的是,Mockito 是相当线程安全的,因此您不仅会抛出异常。

您的场景可能发生的一种方式是,如果线程碰巧 运行 像这样:

  1. 主线程:调用 l.parallelStream().forEach(),生成 3 个线程,val 等于 1、2 和 3
  2. val=1线程:执行finalI=val,在本线程
  3. 中赋值为1
  4. val=2线程:执行finalI=val,在本线程
  5. 中赋值为2
  6. val=3线程:执行finalI=val,在本线程
  7. 中赋值为3
  8. val=3 线程:调用 doAnswer(...) 使将来对 mymock.toString() 的调用打印出 3
  9. val=1 线程:调用 doAnswer(...) 使将来对 mymock.toString() 的调用打印出 1
  10. val=2 线程:调用 doAnswer(...) 使将来对 mymock.toString() 的调用打印出 2
  11. val=1 线程:调用 mymock.toString(),打印出 2
  12. val=2 线程:调用 mymock.toString(),打印出 2
  13. val=3 线程:调用 mymock.toString(),打印出 2

请注意,这不一定按顺序发生,甚至不一定按这个顺序发生:

  • 其中的某些部分可能实际上是同时发生在处理器的不同内核中。
  • 虽然单个线程中的任何步骤总是 运行 有序,但它们可能 运行 在任何其他线程的任何步骤之前、之后或交错。

因为线程是 运行ning 并行的,而且您没有设置任何东西来管理它,所以无法保证这些调用发生的顺序:您的输出可能很容易已经 1 1 11 2 33 2 13 2 21 1 3