collections.synchronizedxxx仅保证单个方法线程安全,不保障复合操作原子性;迭代必须手动同步,推荐优先使用concurrenthashmap、copyonwritearraylist等并发集合。

Java中Collections.synchronizedXXX方法提供了一种快速包装非线程安全集合为线程安全集合的方式,但它不是万能的“开箱即用”方案——它只保证单个操作的原子性,不保证复合操作的线程安全。
同步包装器的本质是加锁代理
这些方法(如synchronizedList、synchronizedMap)返回的是原集合的包装类,内部持有一个互斥锁(默认为集合自身,也可传入自定义锁对象)。每次调用其方法时,都会在该锁上同步执行。例如:
-
Collections.synchronizedList(new ArrayList())返回一个SynchronizedList实例,所有add()、get()、size()等方法都加了synchronized块 - 但迭代器(
iterator())返回的是未同步的底层迭代器,直接遍历时仍需手动同步
迭代操作必须显式加锁
这是最容易踩坑的地方:包装后的集合本身方法是同步的,但iterator()或entrySet().iterator()返回的对象不自动继承同步语义。若在遍历时有其他线程修改集合,仍可能抛出ConcurrentModificationException或产生数据不一致。
- 正确做法是手动对整个遍历过程加锁,锁对象必须与集合包装器使用的锁一致(默认就是集合本身)
- 例如:
synchronized (list) { for (Object e : list) { ... } } - 若构造时指定了自定义锁(如
synchronizedList(list, mutex)),则遍历时也必须synchronized (mutex)
复合操作天然不安全,需额外同步
像“检查是否存在再添加”(if (!map.containsKey(k)) map.put(k, v);)这类逻辑,即使使用synchronizedMap也无法保证原子性,因为两次方法调用之间存在竞态窗口。
- 必须将整个复合操作包裹在同一个同步块中,锁对象与集合包装器一致
- 或者改用更合适的并发集合,如
ConcurrentHashMap提供的computeIfAbsent等原子方法 - 注意:
ConcurrentHashMap不支持containsKey() + put()这种手动组合,而推荐使用其内置原子操作
性能与适用场景权衡
同步包装器采用粗粒度锁(整个集合一把锁),高并发下容易成为瓶颈;而java.util.concurrent包中的集合(如ConcurrentHashMap、CopyOnWriteArrayList)通过分段锁、CAS、写时复制等机制提升并发吞吐量。
- 适合读多写少、并发压力不大、且只需简单同步保障的场景
- 不适合高频写操作或对吞吐量敏感的服务(如Web后端缓存、实时统计)
- 若已有代码大量使用
ArrayList/HashMap,又需快速上线线程安全版本,可作为过渡方案,但应尽快评估迁移到并发集合的可行性
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











