JUnit 官方不提供 ConcurrentRunner,验证线程安全应通过 ExecutorService 构造可控并发场景、CyclicBarrier 同步起始点,并用原子类与断言工具校验结果一致性,避免依赖非标准注解或无效等待。

JUnit 本身不内置 ConcurrentRunner,所谓“ConcurrentRunner”是部分第三方库(如 junit-concurrent)或旧版自定义 Runner 的叫法,并非 JUnit 4/5 官方标准组件。现代 Java 单元测试验证线程安全,关键不在于“用哪个 Runner”,而在于**可控地构造并发场景 + 显式校验结果一致性**。
用 ExecutorService 模拟真实并发压力
这是最直接、可复现、易调试的方式。不依赖特殊 Runner,纯靠 Java 并发工具控制线程数量、任务提交和执行等待。
- 创建固定大小线程池(如
Executors.newFixedThreadPool(10)),避免线程爆炸 - 批量提交相同操作(比如 100 次调用待测方法),每个任务封装为
Runnable或Callable - 调用
executor.shutdown()后,用awaitTermination()等待全部完成,而非仅靠join()(后者需手动管理 Thread 实例) - 执行完后断言最终状态(如共享计数器是否等于预期值)
用 CyclicBarrier 同步起始点,暴露竞态条件
单纯“多线程跑”可能因执行时机错开而漏掉问题。CyclicBarrier 能让所有线程在临界操作前严格对齐,增大竞争概率。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 初始化
CyclicBarrier(n),n 为并发线程数 - 每个线程任务中:先调用
barrier.await()阻塞,直到全部线程就绪 - 紧接着执行待测的共享资源操作(如
counter.increment()) - 这样能更大概率触发读-改-写冲突,使线程不安全行为更容易暴露
结合原子类或断言工具做结果验证
并发测试成败取决于“怎么判断是否安全”,不能只看是否抛异常。
立即学习“Java免费学习笔记(深入)”;
- 对计数类等场景:预期执行 n×m 次后值为 n×m,若结果偏小,说明发生了丢失更新
- 对集合类(如
ArrayList):并发 add 后 size 不等于总次数,或遍历时抛ConcurrentModificationException,即不安全 - 推荐用
AtomicInteger做预期值比对,避免因 int 自增非原子导致断言本身出错 - 可配合
AssertJ的as("final count").isEqualTo(expected)提升可读性
避免常见误区
很多并发测试看似跑了多线程,实则无效。
- 不用
Thread.sleep()模拟并发——时序不可控,结果不可重复 - 不依赖 “没报错=安全”——死锁、饥饿、脏读可能静默发生
- 不把测试逻辑写在
run()里却不等待完成——JUnit 主线程结束即算测试通过,实际任务还在跑 - 慎用
@Concurrent这类非标准注解(来自已停止维护的 junit-concurrent 库),它无法定制 barrier、无法捕获中间异常、难以 debug

















