mockito 本身不直接支持模拟多线程竞态条件,但可通过配合显式线程控制、同步原语和 mock 行为定制,在测试中可靠复现和验证竞态逻辑。

Mockito 本身不直接支持模拟多线程竞态条件(如 race condition),因为它是一个单元测试隔离框架,核心目标是控制依赖行为、验证交互,而非调度线程或注入时序不确定性。但你可以通过组合 Mockito 与显式线程控制、同步原语、以及少量辅助技巧,在测试中**可靠复现和验证竞态逻辑**——关键不是“用 Mockito 模拟线程”,而是“用 Mockito 配合线程控制来测试竞态下的被测代码行为”。
1. 用 Mockito mock 可变共享依赖,并暴露可控的竞态点
竞态通常发生在多个线程竞争修改同一共享状态(如一个 service 调用 DAO 更新数据库计数器)。这时,把共享资源抽象为可 mock 的接口(如 CounterRepository),再在 mock 中加入同步控制点,让线程按需阻塞/唤醒:
CounterRepository mockRepo = Mockito.mock(CounterRepository.class);
Object lock = new Object();
AtomicBoolean firstThreadEntered = new AtomicBoolean(false);
// 让第一个线程走到 update 前暂停,第二个线程进来时触发竞态
Mockito.doAnswer(invocation -> {
synchronized (lock) {
if (!firstThreadEntered.get()) {
firstThreadEntered.set(true);
lock.wait(); // 第一个线程挂起
}
}
return 42; // 正常返回
}).when(mockRepo).incrementAndGet();
这样,你就能精确控制两个线程在关键临界区前的执行顺序,制造出真实竞态场景。
2. 启动多个线程调用被测方法,并用 CountDownLatch 控制并发节奏
避免用 Thread.sleep() 这种不可靠方式。用 CountDownLatch 确保多个线程同时触发目标方法:
- 初始化一个
latch = new CountDownLatch(1)和一个startLatch = new CountDownLatch(n) - 每个线程启动后先
startLatch.countDown(),再latch.await()等待统一信号 - 主线程等所有线程就位后,调用
latch.countDown()—— 所有线程瞬间并发执行
配合上一步的 mock 行为,你就能稳定复现“两个线程几乎同时读-改-写”的过程。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
3. 验证竞态结果:不只看最终值,更要检查中间状态或副作用
比如被测方法本应保证“每次调用使计数器 +1”,但在无锁下可能因竞态导致只 +1 而非 +2。这时:
- 用
AtomicInteger或ConcurrentLinkedQueue记录每次 mock 方法被调用的顺序和参数 - 用
verify(mockRepo, times(2)).incrementAndGet()确认调用次数 - 检查最终状态是否符合竞态预期(例如:期望 2 次调用 → 结果为 1,说明发生了覆盖)
也可以在 mock 的 doAnswer 中抛出异常、记录时间戳、或修改共享 flag,用于断言竞态是否真实发生。
4. 替代方案:用专门工具更自然地建模竞态(推荐进阶使用)
如果需要高频测试竞态逻辑,Mockito + 手动线程控制会变得复杂。此时可考虑:
- JCStress:专为 JVM 并发测试设计,能生成大量微基准并统计各种内存模型下可能的结果分布
-
ConcurrentUnit:提供
Waiter、Actor等 DSL,简化并发断言(如waiter.expect(2).events();) - TestNG + @Test(threadPoolSize=2, invocationCount=100):暴力运行多次,结合 assert 断言“结果必须 ∈ {1,2}”,捕获偶发失败
这些不是 Mockito 的替代,而是互补——Mockito 仍负责隔离外部依赖,它们负责驱动并发和观测。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










