不能直接用 for-each 循环调用 list.remove(),因为会触发 ConcurrentModificationException;这是单线程下 Iterator 的 fail-fast 机制所致,需改用 iterator.remove() 或 removeIf() 等安全方式。

为什么不能直接用 for-each 循环调用 list.remove()
直接在 for (Item item : list) 中执行 list.remove(item) 会触发 ConcurrentModificationException。这是因为增强 for 循环底层依赖 Iterator,而 ArrayList 等集合的 remove() 方法会修改 modCount,但迭代器没被通知,校验失败就抛异常。
这不是线程安全问题,而是单线程下“快速失败”(fail-fast)机制的正常表现。
- 只有
Iterator自己的remove()方法能安全删除当前元素 -
Iterator.remove()会同步更新expectedModCount,避免校验失败 - 必须在调用
next()之后、且只调用一次remove(),否则抛IllegalStateException
正确写法:用 iterator.remove() 配合 while + hasNext()
显式获取迭代器,手动控制遍历和删除节奏,是最稳妥的方式。适用于 ArrayList、LinkedList、HashSet 等绝大多数标准集合。
Iterator<CacheEntry> it = cacheList.iterator();
while (it.hasNext()) {
CacheEntry entry = it.next();
if (entry.isExpired()) {
it.remove(); // ✅ 安全删除
}
}
- 不要在循环体里再调用
it.next()多次,否则可能跳过元素 - 不能在
remove()后立即再调用remove(),会报IllegalStateException - 对
ConcurrentHashMap或CopyOnWriteArrayList不适用——它们不支持Iterator.remove()(会抛UnsupportedOperationException)
替代方案:removeIf() 更简洁但有坑
Collection.removeIf(Predicate) 是 Java 8 引入的语法糖,内部仍用迭代器 + remove(),语义清晰,但要注意:
cacheList.removeIf(entry -> entry.isExpired());
- 它要求集合实现类支持
removeIf(ArrayList支持,Arrays.asList()返回的 List 不支持) - 无法在删除时做额外逻辑(比如记录日志、触发回调),因为它是黑盒批量操作
- 对大列表性能略差于手写迭代器(多一次遍历判断 + 批量移动数组)
- 如果
Predicate抛异常,整个操作中断,已删元素不可回滚
特殊集合:ConcurrentHashMap 和 CopyOnWriteArrayList 怎么办
这些线程安全集合不提供可删除的 Iterator,因为它们的设计目标是无锁并发读写,而非支持遍历时修改。
-
ConcurrentHashMap:用computeIfPresent()或remove(key, value)按 key 清理;或先收集过期 key,再批量removeAll(keys) -
CopyOnWriteArrayList:只能用普通 for 循环倒序遍历(for (int i = list.size()-1; i >= 0; i--)),正序删除会导致索引错位漏删 - 二者都不该在遍历时调用
iterator.remove(),会明确抛UnsupportedOperationException
真正容易被忽略的是:你以为用了线程安全集合就“自动安全”,其实遍历+删除仍是独立操作,必须匹配对应策略。没有银弹,只有按集合契约做事。

















