collections.synchronizedlist仅保证单个方法调用(如add、get)线程安全,复合操作(如检查后添加、遍历时修改)必须手动加锁;正确用法是final声明、匿名构造、遍历和条件操作均需synchronized(synclist)包裹。

用 Collections.synchronizedList 得到的不是“全自动安全”的列表,它只让 单个方法调用(如 add、get、remove)线程安全,但凡涉及多个步骤的操作——比如先判断再添加、遍历时修改、或边遍历边删除——都必须手动加锁,否则照样出错。
初始化必须写对:final + 匿名构造 + 不暴露原始引用
错误写法会让同步完全失效:
-
List<string> raw = new ArrayList(); List<string> sync = Collections.synchronizedList(raw);</string></string>→ 后续若直接操作raw,锁形同虚设 - 变量没声明为
final→ 可能被意外替换,或通过 getter 暴露非同步视图 - 对
syncList调用subList()或传给Arrays.asList()→ 返回的是普通 List,不带任何同步能力
推荐写法:
private final List<string> safeList = Collections.synchronizedList(new ArrayList());</string>
如需初始容量,可写 new ArrayList(16),再包装。
遍历必须显式加锁:synchronized(syncList) { ... }
它的迭代器本身不加锁、不 fail-safe,增强 for 循环(for (String s : syncList))会隐式调用 iterator(),一旦其他线程并发修改,大概率抛 ConcurrentModificationException 或读到不一致状态。
正确做法是把整个遍历逻辑包在同步块里,锁对象必须是那个 syncList 实例本身:
synchronized (safeList) {
for (String item : safeList) {
System.out.println(item);
}
}
如果要用 Iterator,也要在同一个同步块内获取并使用:
synchronized (safeList) {
Iterator<string> it = safeList.iterator();
while (it.hasNext()) {
String s = it.next();
if (s.startsWith("A")) it.remove(); // 安全删除
}
}</string>
哪些操作不用额外加锁?哪些仍危险?
已内置同步的方法(放心用):addAll、removeAll、retainAll、clear、containsAll 等批量方法,内部已用 synchronized(mutex) 包裹。
看似安全、实则危险的操作(必须加锁):
-
if (!list.isEmpty()) list.get(0);→ 中间可能被清空,get(0)报IndexOutOfBoundsException -
if (!list.contains(x)) list.add(x);→ 检查和添加之间存在竞态窗口 -
list.size() > 0 && list.get(0)→ 同上,非原子
这类“检查-执行”逻辑,必须整体包裹在 synchronized(safeList) 块中才能保证原子性。
和 CopyOnWriteArrayList 怎么选?
两者适用场景截然不同:
-
Collections.synchronizedList:适合写操作稍多、读操作不极端频繁、且能接受读写都阻塞的场景;改造成本低,兼容原有
List逻辑,但高并发下易成瓶颈 - CopyOnWriteArrayList:适合读远多于写(如配置监听、事件广播)、要求遍历绝对不抛异常、读线程不能被阻塞的场景;写操作开销大(每次复制数组),内存占用高,且迭代器看到的是快照,看不到最新写入
别混用:把 CopyOnWriteArrayList 再传给 Collections.synchronizedList,只是套了一层无意义的锁,还可能掩盖问题。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











