Collections.synchronizedList仅保证单个操作原子性,遍历时仍可能抛ConcurrentModificationException,需手动同步或改用CopyOnWriteArrayList。
为什么 Collections.synchronizedList 不能完全解决遍历并发问题
它只保证单个操作(如 add、get)是原子的,但迭代过程本身不是原子操作。一旦在 for-each 或 iterator.next() 过程中其他线程修改了集合,就会触发 ConcurrentModificationException。
常见错误现象:多线程环境下反复出现 java.util.ConcurrentModificationException,即使所有写操作都走同步方法。
- 必须手动对整个遍历加锁:
synchronized (list) { for (String s : list) { ... } } - 或者改用
CopyOnWriteArrayList(适合读多写少场景) - 注意:
Collections.synchronizedMap同样不保护keySet()、values()等视图的迭代安全
ConcurrentHashMap 的迭代器为什么不会抛 ConcurrentModificationException
它的迭代器基于分段快照(JDK 8+ 是基于 Node 数组的弱一致性遍历),不要求遍历时集合结构完全冻结,允许其他线程并发修改——但不保证看到最新修改。
使用场景:高并发读写、需要容忍“过期读”的缓存或计数器。
立即学习“Java免费学习笔记(深入)”;
-
size()不是 O(1),可能需多次尝试才能获取较准值 -
putIfAbsent、computeIfAbsent是原子的,比手动synchronized+containsKey更安全高效 - 禁止在遍历中调用
remove或put修改当前正在遍历的 key(虽不抛异常,但行为未定义)
什么时候该选 CopyOnWriteArrayList 而不是 synchronizedList
当读操作远多于写操作,且写操作频率极低(比如配置监听器列表、事件回调注册表),CopyOnWriteArrayList 的“读无锁”优势才真正体现出来;否则写操作的数组复制开销反而更重。