collections.synchronizedlist仅保证单方法原子性,不保多步逻辑原子性;遍历需手动synchronized(list)且锁对象必须一致;原始引用不可暴露,推荐final声明;适用于读多写少、元素大、强顺序一致性场景。

它只保单步操作,不保逻辑原子性
Collections.synchronizedList 对 add、get、size 等每个 public 方法内部加了 synchronized,但仅限该方法调用本身。一旦涉及多步判断或组合动作,比如“先检查是否包含某元素,再添加”,中间就可能被其他线程插入或删除,导致逻辑错乱甚至 IndexOutOfBoundsException。
常见出问题的写法包括:
- if (!list.contains(x)) list.add(x); —— 两步之间无锁保护
- if (!list.isEmpty()) list.remove(0); —— size() 和 remove(0) 不是原子对
- for (String s : list) { ... } —— 增强 for 底层调用 iterator(),而迭代器本身未同步
遍历必须手动加锁,且锁对象要一致
它的 iterator() 返回的是非同步迭代器,不能在多线程中直接使用。所有遍历操作必须包裹在 synchronized(list) 块中,且锁对象必须是那个被包装后的列表实例本身。
错误做法示例:
- 用 synchronized(new Object()) —— 锁对象不同,等于没锁
- 把列表传入工具方法后,在方法内没加锁 —— 同步被绕过
- 用 subList() 或 Arrays.asList() 包装 syncList —— 返回视图不带同步能力
原始引用不能暴露,变量建议声明为 final
如果先创建 ArrayList,再用 Collections.synchronizedList 包装,但又把原始 ArrayList 赋值给另一个变量并直接操作,那同步完全失效。包装器只保护自己暴露的方法入口,不控制底层对象行为。
安全初始化推荐写法:
- private final List
safeList = Collections.synchronizedList(new ArrayList()); - 避免分两步写:List
raw = new ArrayList(); List ,之后又用 raw.add(...)sync = Collections.synchronizedList(raw);
性能与适用场景有明显边界
它采用独占锁机制,读写都互斥,吞吐量低,不适合高并发写场景。相比 CopyOnWriteArrayList,它不复制数据、内存友好,但要求你严格管控访问路径;相比 ConcurrentHashMap 模拟集合语义,它不支持高性能并发查找。
适合它的真实场景是:
- 读远多于写,且写操作基本不涉及遍历(如监听器注册表)
- 列表元素较大(含 byte[]),复制成本高
- 需要强顺序一致性,不能接受快照式迭代(CopyOnWriteArrayList 迭代看不到最新写入)
- 已有代码重度依赖 List 接口,改造成本高,需最小侵入方案
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











