arraylist非线程安全,多线程遍历时修改会触发concurrentmodificationexception;推荐用copyonwritearraylist或collections.synchronizedlist,或手动同步、改遍历方式、换并发集合。

Java 中 ArrayList 本身不是线程安全的,多线程环境下一边遍历一边修改(或多个线程同时增删),会大概率触发 ConcurrentModificationException。这不是偶然报错,而是它内部 fail-fast 机制主动抛出的提示,目的是暴露未受控的并发访问问题。
用线程安全的替代集合
这是最直接、推荐优先考虑的方式:
- CopyOnWriteArrayList:适合读远多于写的场景(如监听器列表、配置缓存)。每次 add/remove 都复制底层数组,迭代器基于快照工作,不会抛 CME,也不需要额外加锁;但写操作开销大、内存占用高、遍历时看不到最新写入的数据。
-
Collections.synchronizedList(new ArrayList()):给 ArrayList 套一层同步包装,所有单个方法(add/get/remove)都串行执行;但遍历时仍需手动加锁,否则依然可能报错,例如:
synchronized (list) { for (E e : list) { ... } }
手动加锁控制临界区
如果必须用原生 ArrayList,且对锁粒度或语义有明确要求,可显式同步所有读写操作:
- 所有增删改查、以及 for-each / iterator 遍历,统一用同一个锁对象包裹;
- 避免在锁内做 I/O、网络调用等耗时操作;
- 若多个逻辑相关的列表(如订单列表 + 订单状态映射)共用业务上下文,建议共用一把锁,而非各自加锁。
改变使用方式,避开边遍历边修改
很多 CME 其实源于编码习惯,不改结构就能彻底规避:
- 遍历时只读,把要删的元素先收集到临时 List(
toRemove),遍历完再调用list.removeAll(toRemove); - 必须在遍历中删当前元素时,只用
iterator.remove(),绝不用list.remove(); - 用倒序 for 循环(
i = size-1; i >= 0; i--)删除,避免索引偏移; - 初始化后不再变更的列表(如系统常量),转为不可变集合:
List.copyOf(original)或 Guava 的ImmutableList。
按场景选更合适的并发模型
如果业务允许结构调整,可以跳出“一定要用 ArrayList”的思维:
- 需要键值映射 + 并发读写 → 改用
ConcurrentHashMap,再用values()转为视图; - 纯只读批量处理 → 用
list.parallelStream(),注意避免副作用; - 数据分片处理 → 按索引范围切分(如每段 1000 个),配合
ExecutorService并行遍历。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











