fail-fast机制通过concurrentmodificationexception暴露非法并发修改,测试需构造“遍历+修改”冲突场景验证异常是否必现,并检验线程安全替代方案的行为正确性及业务一致性。

Java 集合迭代失效机制(即 fail-fast)本身不是“要被测试通过”的功能,而是设计用来在多线程非法共享迭代器或并发修改时主动暴露问题——一旦触发 ConcurrentModificationException,就说明当前迭代方式不安全。因此,“测试迭代安全性”的本质是:验证你的多线程遍历方案是否真正规避了该异常,并符合业务一致性要求。
构造可复现的并发冲突场景
用两个及以上线程对同一非线程安全集合(如 ArrayList)做“一边遍历、一边修改”的操作,是最直接的验证方式:
- 线程 A 调用
list.iterator()后持续next()遍历 - 线程 B 在 A 遍历中途调用
list.add()或list.remove() - 观察线程 A 是否在下一次
next()时抛出ConcurrentModificationException
这个测试不依赖运气——只要集合未加同步、迭代器未隔离,几乎必现。它能快速确认 fail-fast 是否生效,也反向验证你后续采用的方案(如 CopyOnWriteArrayList)是否真的绕开了该检查。
验证线程安全替代方案的实际行为
光不抛异常还不够,得看结果是否符合预期。例如:
- 用
CopyOnWriteArrayList:启动遍历后,在另一线程添加元素,检查迭代器是否只看到旧快照(不包含新元素),且不抛异常 - 用
ConcurrentHashMap的entrySet().iterator():插入新键值对后继续遍历,确认不会漏项也不会重复,但也不保证一定看到新增项(弱一致性) - 用分片 + 独立
subList:确保每个线程处理的是复制后的独立ArrayList,而非原集合视图,否则仍可能因原集合被改而触发异常
检查是否误用“伪安全”写法
有些写法看似规避了异常,实则引入逻辑错误,需重点排查:
-
局部变量拷贝集合再遍历(
List<t> snapshot = new ArrayList(original)</t>):能避免 CME,但数据已静态,无法响应实时变更 - synchronized 包裹整个 for-each 循环:虽不抛异常,但把并行遍历退化为串行,违背并发初衷,还可能引发锁竞争或死锁
-
在增强 for 循环中调用
list.remove():即使单线程也会崩,必须改用iterator.remove()
关注内存可见性与业务语义一致性
不抛异常 ≠ 安全。例如:
- 用
Collections.synchronizedList()包装后,多个线程各自创建迭代器遍历——不会抛 CME,但各线程看到的数据状态可能不同步(无全局一致性保证) - 用
parallelStream().forEach()处理有副作用的操作(如累加共享计数器),若未用AtomicInteger或加锁,结果可能错乱 - 业务上要求“遍历过程中不能遗漏任何新写入的元素”,那
CopyOnWriteArrayList就不合适,应考虑队列类(如BlockingQueue)或消息通知机制
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











