collections.synchronizedlist仅对单个方法加锁,无法保证复合操作的线程安全,遍历时需手动同步或用快照,且须遵循final声明、不绕过原始列表、不创建非同步视图等约束。

它只锁单个方法,不保业务逻辑安全
Collections.synchronizedList 返回的不是“全自动线程安全”的集合,而是一个装饰器:对 add、get、remove、size 等每个 public 方法加了 synchronized(this)。这意味着单次调用不会出错,但多个方法组合起来的逻辑(比如“先检查再添加”)依然可能被其他线程插队破坏。
典型错误示例:
if (!list.contains(x)) list.add(x); —— 两步之间,另一线程可能已插入 x,导致重复;
if (!list.isEmpty()) list.get(0); —— 判空后、取元素前,列表已被清空,抛 IndexOutOfBoundsException。
正确做法是把整段逻辑包进同步块:
synchronized (list) {
if (!list.contains(x)) list.add(x);
}
遍历必须手动加锁,否则大概率崩溃
它的 iterator() 返回的是底层原始 List 的迭代器,**未做任何同步处理**。增强 for 循环(for (E e : list))本质就是调 iterator(),若遍历时有其他线程修改列表,几乎必然触发 ConcurrentModificationException。
安全遍历只有两种可靠方式:
- 显式同步整个遍历过程:
synchronized (list) {
for (String s : list) { /* 安全 */ }
} - 快照式遍历(推荐,无锁无阻塞):
Listsnapshot = new ArrayList(list);
for (String s : snapshot) { /* 处理副本,原 list 可并发修改 */ }
注意:不要在 synchronized 块里长期持有迭代器或执行耗时操作,避免锁粒度过大。
初始化和使用中的关键细节
真正让 synchronizedList 发挥作用,依赖三个硬性约束:
- 变量必须声明为 final:防止被意外替换或暴露原始引用;
-
原始列表不能被绕过访问:比如
List<string> raw = new ArrayList(); List<string> sync = Collections.synchronizedList(raw);</string></string>,之后又直接用raw操作——锁完全失效; -
避免创建非同步视图:不要对 syncList 调用
subList()或传给Arrays.asList(),这些返回的对象不带同步能力。
标准初始化写法:
private final List
什么时候该换别的方案
synchronizedList 是低侵入、快上手的折中选择,但不是万能解。遇到以下情况,建议切换:
- 读远多于写(如监听器列表、配置缓存) → 选 CopyOnWriteArrayList:遍历绝对安全、无锁,但写操作复制数组,内存和 CPU 开销明显;
- 写操作频繁(如每秒几十次以上)且需强一致性 → 考虑 ConcurrentLinkedQueue(队列语义)或自定义分段锁结构;
- 已有大量 List 接口调用,又需原子复合操作 → 不要硬扛,优先重构为 CopyOnWriteArrayList 或引入显式 ReentrantLock 控制临界区。
别把 CopyOnWriteArrayList 再传给 Collections.synchronizedList——纯属冗余,还可能掩盖问题。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











