ArrayList非线程安全,多线程读写易引发ConcurrentModificationException或数据异常;应通过CopyOnWriteArrayList、synchronizedList、手动加锁、遍历修改分离、不可变集合等方式从设计上规避竞态条件。

ArrayList 本身不提供线程安全保证,多线程同时读写(尤其是边遍历边修改)会直接触发 ConcurrentModificationException,或导致数据丢失、越界、size 错误等不可预测行为。解决核心不是“捕获异常”,而是从设计上避免竞态条件。
用线程安全的替代集合
这是最常用、最直接的方案,按场景选:
- CopyOnWriteArrayList:适合读远多于写的场景(如监听器列表、配置缓存)。读操作无锁、不阻塞;写操作加锁并复制整个数组,开销大,但迭代时不会抛 CME,也看不到其他线程刚写入的数据。
-
Collections.synchronizedList(new ArrayList()):所有方法加同一把对象锁,读写都串行化。简单可靠,但迭代仍需手动同步:
synchronized(list) { for (E e : list) { ... } },否则仍可能出 CME。 - Vector:历史遗留类,方法级 synchronized。语义清晰,但粒度粗、性能低,不推荐新项目使用。
手动加锁统一控制访问
当必须用 ArrayList(比如已有大量依赖、或需精细控制锁范围),可自行加锁:
当用户要求为 Java 代码添加注释、修改注释、审查注释、整理注释格式,或要求按照代码注释规范处理 Java 文件时,使用本技能。本技能用于统一生成和维护 Java 类注释模板、按 Git 对比 release 判断版本号、补充每行代码上方中文块注释、调整注释空行规则,并保持 Java 文件 UTF-8 无 BOM 编码。典型触发场景包括:补充类注释、更新 @version、检查 @author 来源、整理行尾注释、把注释放到代码上一行、检查注释是否符合规范、构建前检查 Java 注释风格。
- 所有增删改查、以及
for-each或iterator遍历,都包裹在同一个synchronized块或ReentrantLock中; - 避免在锁内做 I/O、网络调用等耗时操作;
- 若多个逻辑相关的 ArrayList(如订单列表 + 订单状态 Map)共属一个业务单元,建议共用一个锁对象,而非各自加锁。
改写代码逻辑,避开遍历时修改
很多 CME 其实源于编码习惯。只要不边遍历边结构性修改,就能彻底规避:
- 遍历时只读,要删/加的元素先暂存到另一个集合(如
List<integer> toRemove = new ArrayList()</integer>),遍历完再统一调list.removeAll(toRemove); - 必须在循环中删除当前元素时,用
Iterator.remove()—— 这是唯一被允许的遍历中修改方式; - 避免在增强 for 循环体内调
list.add()或list.remove(); - 批量操作优先用
list.removeIf()、list.replaceAll()等 JDK 内置方法,它们已内部协调modCount。
用不可变集合固化只读数据
如果列表初始化后就不再变更(如系统常量、枚举映射、配置项),转为不可变集合是最省心的选择:
- JDK 10+:用
List.copyOf(originalList); - Guava:用
ImmutableList.copyOf(list); - 不可变集合天然线程安全,内存共享、无并发风险,任何修改操作都会立即抛
UnsupportedOperationException,便于早期暴露错误。

















