JUnit并发测试需确保断言在主线程执行,用CountDownLatch同步等待并检查超时,通过AtomicReference捕获子线程异常,推荐CompletableFuture统一编排,避免共享可变状态。

JUnit 本身是单线程设计,直接在多线程中调用 assert 可能因线程调度导致断言在非主线程执行而被忽略或抛出未捕获异常,进而让测试“假通过”。要写健壮的并发单元测试,核心是:确保所有断言发生在主线程、正确同步共享状态、捕获并传播子线程中的异常。
用 CountDownLatch 等待所有线程完成再断言
避免在子线程里写 assert;把校验逻辑移回主线程。CountDownLatch 是最常用手段,它让主线程阻塞等待指定数量的线程完成任务。
- 初始化
CountDownLatch latch = new CountDownLatch(n),n 为并发线程数 - 每个工作线程执行完业务逻辑后调用
latch.countDown() - 主线程调用
latch.await(5, TimeUnit.SECONDS)等待超时,再统一做断言 - 务必检查超时情况:
if (!latch.await(...)) fail("timeout")
捕获并重新抛出子线程中的异常
子线程内未捕获的异常不会传播到主线程,JUnit 就感知不到失败。需显式收集异常:
- 声明一个
AtomicReference<throwable> errorRef = new AtomicReference()</throwable> - 在线程 run 方法中 try-catch,一旦异常就
errorRef.set(e) - await 后立即检查:
Throwable t = errorRef.get(); if (t != null) throw t; - 也可用
Thread.UncaughtExceptionHandler全局兜底(但推荐显式处理)
使用 CompletableFuture 统一编排和断言
比裸线程更可控,天然支持异常传播和组合等待:
立即学习“Java免费学习笔记(深入)”;
- 启动多个
CompletableFuture<void>.runAsync(() -> { ... })</void> - 用
CompletableFuture.allOf(f1, f2, ...).join()阻塞等待全部完成(任一异常都会抛出) - 若需获取返回值或分步验证,可用
thenApply或whenComplete在主线程处理结果 - 注意:不要在 lambda 中直接 assert,仍应等 join 后再断言最终状态
避免共享可变状态,优先用线程安全结构或局部变量
并发测试失败常源于竞态,而非逻辑错误。减少共享变量能大幅降低不确定性:
- 用
ConcurrentHashMap、AtomicInteger替代普通 HashMap / int - 若只是计数,优先用
AtomicInteger而非加锁的 int - 每个线程操作自己的对象实例(如 new ArrayList()),最后合并结果再断言
- 测试前重置静态/全局状态(如用 @BeforeEach 清理单例缓存)


















