JUnit中测试多线程并发逻辑的核心是主动暴露竞态、控制时序、验证一致性,需用CyclicBarrier对齐线程起始点,配合自旋等待确保状态收敛,并在共享实例中建模真实竞争场景。

在 JUnit 中测试多线程并发逻辑,核心不是“让线程跑起来”,而是**主动暴露竞态、控制时序、验证一致性**。单纯用 Thread.start() 或 ExecutorService 提交任务,往往因执行时机随机而漏掉问题——线程可能错开执行,看似通过,实则掩盖隐患。
用 CyclicBarrier 对齐起始点,放大竞争概率
竞态条件(Race Condition)只在多个线程“恰好”交错访问共享状态时触发。CyclicBarrier 能强制所有线程在临界操作前严格同步,大幅提升复现概率。
- 初始化
CyclicBarrier barrier = new CyclicBarrier(n + 1),其中n是工作线程数,+1 是主线程参与等待 - 每个工作线程启动后立即调用
barrier.await(),阻塞直到全部就绪 - 主线程也调用
barrier.await(),表示“可以开始了”,所有线程同时冲向临界区 - 这样能稳定复现如“读-改-写”未加锁导致的计数丢失等问题
避免断言失效:等真正完成,再检查结果
并发测试失败常源于“检查太早”,而非逻辑错误。不能只靠 join() 或 executor.shutdown() 就断言,要确认状态已最终收敛。
- 对共享变量(如计数器),使用
AtomicInteger或volatile配合自旋等待,例如:while (counter.get() != expected) Thread.sleep(1); - 若涉及外部状态(如文件、数据库),应设计可轮询的校验逻辑,而非依赖固定休眠
- JUnit 5 可结合
assertTimeoutPreemptively防止无限等待,同时保证超时后能清理资源
不依赖 @Concurrent 注解,手动建模更可控
某些旧版扩展(如 @Concurrent)只是重复执行测试方法,并非真正并发——它启动的是独立实例,无共享状态,无法测竞态。真实并发测试必须:
立即学习“Java免费学习笔记(深入)”;
- 在同一个测试实例中创建多个线程,共用被测对象和共享字段
- 明确区分“被测逻辑”与“同步机制”:比如测试一个
Counter类,就让它本身不带锁;测试目的就是看它在并发下是否出错 - 用
synchronized、ReentrantLock或AtomicInteger改写后再次运行,对比结果变化,才能验证修复效果
识别典型竞态模式,针对性设计测试场景
不必盲目并发,聚焦常见漏洞点设计用例:
- 先检查后执行:如“文件不存在才创建”,用两个线程交替执行判断与创建
- 延迟初始化:测试单例双重校验锁(DCL)是否真能防止多次构造
-
复合操作非原子:如
if (list.isEmpty()) list.add(x),用多个线程反复执行,观察是否出现重复添加或IndexOutOfBoundsException - 每种场景都搭配 CyclicBarrier + 多轮重试(如 100 次),提高捕获率


















