Java并发测试需主动构造多线程环境、同步观察共享状态并验证一致性或异常;常用方法包括CountDownLatch控制启动时机、AtomicInteger避免数据竞争、@RepeatedTest增强稳定性、ConcurrentUnit简化线程行为定义、Awaitility实现可靠异步断言。

Java 多线程并发测试不能直接用普通 JUnit 单元测试写法,因为常规 @Test 方法是单线程顺序执行,无法真实模拟竞态、线程交错、锁争用等场景。核心思路是:**主动构造多线程环境 + 同步观察共享状态 + 验证最终一致性或异常行为**。
用 JUnit 原生方式启动多线程(适合简单场景)
不依赖额外框架,用 Thread + CountDownLatch + volatile/Atomic 变量控制同步和断言:
- 用 CountDownLatch 确保所有线程同时开始(避免“假并发”)
- 用 AtomicInteger 或 volatile boolean 记录共享状态变化,避免数据竞争干扰断言
- 主线程调用 latch.await(timeout, TimeUnit) 等待全部完成,再检查结果
- 示例:测试一个非线程安全的计数器 increment() 是否丢失更新
用 JUnit 5 的 @RepeatedTest + 并发线程池(增强稳定性)
单次并发测试可能偶然通过,加重复执行可暴露概率性问题:
- 配合 ExecutorService 提交固定数量任务(如 100 个线程各执行 100 次 increment)
- @RepeatedTest(5) 多轮运行,每次重新初始化被测对象
- 每轮 assert 结果是否等于预期总数(如 100×100 = 10000),失败即说明存在竞态
- 注意:必须在每次测试前重置共享资源,否则状态残留导致误判
用 ConcurrentUnit 简化并发断言逻辑
ConcurrentUnit 是专为并发测试设计的轻量库,提供线程感知的断言机制:
立即学习“Java免费学习笔记(深入)”;
- 引入 TestContext,支持 await() 等待某条件成立(如“某个线程已进入临界区”)
- 用 thread("t1").run(() -> {...}) 显式定义线程行为,比 raw Thread 更清晰
- 支持 expectThrows() 捕获预期中的并发异常(如 ConcurrentModificationException)
- 适合验证 synchronized、ReentrantLock、CAS 失败路径等复杂交互
用 Awaitility 写可读性强的异步断言
当被测逻辑涉及异步通知、状态轮询或超时等待时,Awaitility 比手动 sleep + while 更可靠:
- 写法如:await().atMost(2, SECONDS).until(() -> counter.get() == 1000)
- 自动重试 + 指数退避,避免因调度延迟导致的偶发失败
- 配合 CompletableFuture 或 BlockingQueue 场景特别有用(如生产者-消费者边界测试)
- 注意:它不创建线程,只是等待条件,仍需配合 Executor 或 Thread 启动并发操作
不复杂但容易忽略的是:并发测试一定要有明确的“可观测终点”——要么是共享变量的最终值,要么是抛出的异常,要么是某个回调是否被触发。没有终点的并发测试,只是在跑,不是在测。


















