多线程遍历普通集合易引发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());
}
}</string></string>
改用并发集合类
针对不同集合类型,JDK 提供了专为并发设计的替代品,无需手动加锁,语义更清晰:
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
-
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,遍历时仍需外部同步
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










