Collections.synchronizedList仅保证单方法调用线程安全,不保障复合操作原子性,如遍历中删除会抛ConcurrentModificationException;迭代器、跨方法操作(如check-then-act)及外部修改原始底层数组均需额外同步。

为什么 Collections.synchronizedList 不能直接解决并发修改问题
它只是给每个方法加了 synchronized,但不保证复合操作原子性。比如遍历时一边迭代一边删元素,照样抛 ConcurrentModificationException——因为迭代器自己没锁住整个遍历过程。
常见错误现象:for (String s : list) { if (s.isEmpty()) list.remove(s); } 在多线程下大概率崩,哪怕 list 是 Collections.synchronizedList(new ArrayList())。
- 所有迭代操作(
iterator()、forEach()、增强 for)都需手动同步外部锁 -
list.iterator()返回的Iterator本身不是线程安全的,它的next()和hasNext()不受同步保护 - 如果多个线程分别调用
get(i)和remove(i),仍可能因 i 失效导致越界或删错
什么时候该用 Collections.synchronizedList,而不是 CopyOnWriteArrayList
它适合读多写极少、且写操作基本是追加(add())或整体替换(clear())的场景;而 CopyOnWriteArrayList 适合遍历极其频繁、写操作极少(尤其是不涉及随机位置修改)的场景。
性能差异很实在:synchronizedList 每次读都要进锁,争抢严重;CopyOnWriteArrayList 读完全无锁,但每次写都要复制整个数组,内存和 CPU 开销大。
立即学习“Java免费学习笔记(深入)”;
- 如果写操作包含大量
set(int, E)或remove(int),别用CopyOnWriteArrayList—— 它内部没有高效随机更新机制 - 如果集合大小经常超过几千,又频繁写,
synchronizedList的锁粒度太粗,考虑分段锁(如ConcurrentHashMap改造成索引映射)或更细粒度控制 -
synchronizedList返回的是装饰器对象,原底层数组仍可被意外暴露(比如传入new ArrayList(decoratedList)),导致绕过同步
必须配 synchronized(list) 才安全的典型操作
任何跨多个方法调用的逻辑,只要依赖中间状态,就必须把整块代码包在同一个锁里。最典型的就是“检查后执行”(check-then-act)模式。
if (!list.contains("foo")) {
list.add("foo"); // 非原子!两个线程可能同时通过 if,然后都 add
}
正确写法:
synchronized (list) {
if (!list.contains("foo")) {
list.add("foo");
}
}
- 遍历 + 修改:必须用
synchronized (list) { Iterator it = list.iterator(); while (it.hasNext()) { ... } } - 批量操作如
list.addAll(anotherList):虽然addAll方法本身同步了,但如果anotherList是非线程安全的,它可能在迭代时被其他线程改写 - 不要用
synchronized (new Object())或任意新对象当锁——必须锁住 list 本体,否则不同线程锁的不是同一个对象
Collections.synchronizedList 的初始化陷阱
最容易忽略的一点:传进去的原始 list 如果已被其他代码持有引用,就等于开了后门。同步只作用于装饰器接口,不封禁底层 list 的直接访问。
错误示范:
List<String> raw = new ArrayList<>(); List<String> sync = Collections.synchronizedList(raw); // 其他地方还在用 raw.add(...) —— 同步完全失效
- 永远用私有 final 字段保存同步后的 list,并不暴露原始 list 引用
- 如果必须从已有 list 构建,优先用
new ArrayList(source)再包装,避免引用泄漏 - 注意
synchronizedList不会复制元素,如果元素本身可变且被共享,仍需额外同步其状态










