collections.synchronizedlist()不能真正解决并发修改问题,因其仅保证单个操作原子性,不保护复合操作(如遍历删除、检查后添加),迭代器未加锁导致concurrentmodificationexception频发,且keyset等视图非线程安全。

为什么 Collections.synchronizedList() 不能真正解决并发修改问题
它只保证单个操作原子,不保证复合操作安全。比如遍历 + 删除、检查再添加这类“先读后写”逻辑,包装器完全不管。
- 常见错误现象:
ConcurrentModificationException依然会抛出,尤其在多线程遍历时 - 使用场景:仅适合「每个线程只调用单个方法(如
add()或get())」的极简并发,实际业务几乎不存在 - 根本原因:内部用的是
synchronized(this)锁住集合对象本身,但迭代器自己没加锁,iterator().next()和remove()是两个独立同步块
Collections.synchronizedMap() 的迭代陷阱
和 List 一样,keySet()、entrySet() 返回的视图不是线程安全的——你拿到的 Set 对象本身没被同步保护。
- 典型翻车点:用
for (String k : map.keySet()) { map.remove(k); }多线程执行 →ConcurrentModificationException - 参数差异:传入的原始
Map被包装后,所有put/get方法加了锁,但keySet()返回的是原始HashMap.KeySet实例,没额外包装 - 性能影响:锁粒度是整个 map,高并发下容易成为瓶颈;而
ConcurrentHashMap分段/桶级锁,吞吐量高得多
哪些操作看似安全实则危险
包装器对「条件性更新」毫无防护能力,这是最容易被忽略的盲区。
-
if (!list.contains(x)) list.add(x);—— 检查和添加之间可能被其他线程插入相同元素 -
map.putIfAbsent(k, v)不可用:包装后的Map没实现ConcurrentMap接口,putIfAbsent()只是普通方法,不带原子语义 -
list.size() == 0 ? null : list.get(0)—— size 和 get 是两次独立同步调用,中间 list 可能已被清空
替代方案选型关键看这三点
别硬扛包装器,该换就换。选错容器比写错逻辑更难 debug。
- 读多写少 + 需要强一致性:用
CopyOnWriteArrayList,但注意写操作开销大、迭代器看不到最新写入 - 通用高并发键值场景:直接上
ConcurrentHashMap,它支持computeIfAbsent()、replace()等原子复合操作 - 需要阻塞等待或精确控制并发策略:考虑
java.util.concurrent下的BlockingQueue或显式ReentrantLock+ 手动同步
真正麻烦的从来不是加不加 synchronized,而是你以为加了就万事大吉——结果 bug 藏在时序里,复现靠玄学。










