ArrayList非线程安全,多线程遍历易出ConcurrentModificationException;安全方案有:1. synchronizedList+显式同步;2. 创建不可变快照(如List.copyOf);3. 替换为CopyOnWriteArrayList;4. 使用ReentrantReadWriteLock控制读写。

ArrayList 本身不是线程安全的,直接在多线程中遍历(比如用 for-each 或迭代器)可能抛出 ConcurrentModificationException,或读到不一致的数据。要“安全遍历”,核心是避免遍历时底层结构被其他线程修改。以下是几种实用、明确的方案:
使用 Collections.synchronizedList 包装后加显式同步遍历
这是最直接的兼容方式,适合已有 ArrayList 且改动小的场景。
- 先用
Collections.synchronizedList(new ArrayList())创建线程安全包装类 - 但注意:仅包装不能保证遍历安全,必须对整个遍历过程加 synchronized 块,锁对象为该列表本身
- 示例:
List<string> list = Collections.synchronizedList(new ArrayList()); // ... 添加元素 synchronized (list) { for (String s : list) { // 安全遍历 System.out.println(s); } }</string>
遍历时创建不可变快照(推荐)
适用于读多写少、允许短暂延迟的场景。本质是把遍历和修改解耦。
- 用
new ArrayList(originalList)或List.copyOf(originalList)(Java 10+)生成副本 - 在副本上遍历,完全不受原列表并发修改影响
- 代价是内存和复制开销,但逻辑清晰、无锁、无异常风险
- 示例:
List<string> snapshot = List.copyOf(sharedList); // 不可变副本(Java 10+) for (String s : snapshot) { process(s); }</string>
改用线程安全的替代集合
如果写操作也频繁,建议从源头替换为专为并发设计的集合。
-
CopyOnWriteArrayList:遍历时自动获得当前快照,不怕并发修改;适合读远多于写的场景(如监听器列表) - 遍历无需同步:
for (String s : cowList) { ... }天然安全 - 注意:每次写操作都复制整个数组,写性能差,不适合高频更新
用并发工具控制访问节奏
当需要强一致性(比如遍历结果必须反映某一时刻的完整状态),且不能接受快照延迟时,可用显式锁协调。
- 用
ReentrantReadWriteLock:读锁允许多个线程同时遍历,写锁独占 - 遍历前获取读锁,写操作前获取写锁
- 比 synchronized 更灵活,支持升降级,但代码稍重
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











