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 的“读无锁”优势才真正体现出来;否则写操作的数组复制开销反而更重。
性能影响:每次 add、remove 都会复制整个底层数组,时间复杂度 O(n)。
- 迭代绝对安全,无需额外同步
- 迭代器不支持
remove()(调用会抛UnsupportedOperationException) - 无法保证不同迭代器之间的顺序一致性(两次
iterator()可能看到不同快照)
BlockingQueue 类型(如 LinkedBlockingQueue)在生产者-消费者模型中的关键约束
它不是为通用集合设计的,而是专用于线程间数据传递:插入/移除操作自带阻塞语义,天然适配等待-通知逻辑,避免手写 wait/notify。
容易踩的坑:
-
offer()和put()行为完全不同:put()会阻塞直到有空间,offer()直接返回false;误用会导致消息静默丢失 -
poll()(非阻塞)和take()(阻塞)也需严格按语义选用 - 容量设为
Integer.MAX_VALUE的LinkedBlockingQueue实际上退化为无界队列,可能引发 OOM
并发控制的本质不是“加锁”,而是匹配场景选择合适的数据结构契约——错把 synchronizedList 当万能解,往往意味着没看清读写比例、迭代需求和修改粒度这些实际约束。










