多线程遍历普通集合易引发ConcurrentModificationException,因非线程安全且迭代器fail-fast;应使用同步包装器加显式锁、并发集合类(如CopyOnWriteArrayList、ConcurrentHashMap)或原子操作替代直接遍历。

在多线程环境下直接遍历普通集合(如 ArrayList、HashMap、HashSet)极易引发 ConcurrentModificationException 或读到不一致的数据,根本原因在于这些集合本身不是线程安全的,且其迭代器采用 fail-fast 机制——只要检测到结构被修改(哪怕只是另一个线程调用了 add() 或 remove()),就立即抛异常。
用同步包装器 + 手动加锁遍历
这是最通用、可控性最强的方式,适用于已有代码难以重构、或写操作较频繁的场景:
- 先用
Collections.synchronizedList(new ArrayList())等方式获取线程安全的包装集合 - 但注意:遍历操作本身仍需显式加锁,否则仍不安全。因为同步包装器只保证单个方法(如
get()、add())原子,不保证迭代过程整体一致 - 遍历时必须用
synchronized(集合对象)包裹整个迭代逻辑,确保期间无其他线程修改结构
示例:
List<String> list = Collections.synchronizedList(new ArrayList<>());
list.add("a"); list.add("b");
// 安全遍历写法
synchronized (list) {
Iterator<String> it = list.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
}
改用并发集合类
针对不同集合类型,JDK 提供了专为并发设计的替代品,无需手动加锁,语义更清晰:
立即学习“Java免费学习笔记(深入)”;
-
CopyOnWriteArrayList:适合读远多于写的场景(如监听器列表、配置白名单)。遍历时完全无锁,且迭代器基于快照,不会受写操作影响;但每次写都复制整个数组,写开销大 -
ConcurrentHashMap:Map 场景首选。支持高并发读写,迭代时不会抛ConcurrentModificationException,且能反映部分写入结果(弱一致性) -
CopyOnWriteArraySet:Set 场景对应方案,原理同CopyOnWriteArrayList
注意:ConcurrentHashMap 的 keySet()、values()、entrySet() 返回的集合也支持安全遍历,但不保证强一致性(例如遍历时可能看不到刚 put 的元素)
避免遍历,改用原子操作
如果遍历目的只是过滤、查找或批量处理,优先考虑不暴露迭代过程的 API:
- 用
Collection.removeIf(Predicate)替代边遍历边删 - 用
stream().filter(...).forEach(...)处理只读逻辑(注意:流操作本身不解决线程安全,需确保源集合安全) - 对
ConcurrentHashMap,可用forEach(BiConsumer)、search(BiFunction)等内置并发方法,内部已做分段处理
不推荐的做法
以下方式看似简单,实则危险:
- 在增强 for 循环(
for (T t : list))中调用list.remove()—— 单线程都会崩 - 仅用
Collections.synchronizedXxx()包装,却不给遍历加锁 —— 多线程下可能读到中间态或异常 - 用
Vector或Hashtable并假设“方法同步=遍历安全”—— 它们同样 fail-fast,遍历时仍需外部同步


















