Collections.synchronizedSet仅保证单个操作原子性,复合操作和迭代需手动同步,适用于读多写少场景;推荐使用ConcurrentHashMap.newKeySet()或ConcurrentSkipListSet等现代并发集合。

Collections.synchronizedSet 提供的是一个线程安全的 Set 包装器,但它本身不解决所有并发问题,使用时需明确其边界和配合手段。
包装后的 Set 仅保证单个操作原子性
它通过内部锁(默认为传入集合本身)对 add、remove、contains 等方法加 synchronized,确保每个方法调用是原子的。但多个操作组合(如“检查是否存在再添加”)仍可能出错:
- 例如:
if (!set.contains(x)) set.add(x);不是原子操作,两个线程可能同时通过检查并重复添加(尽管 Set 本身去重,但逻辑上可能违背业务意图) - 迭代操作(
for (E e : set))必须手动同步,否则可能抛 ConcurrentModificationException 或读到不一致状态
必须显式同步迭代过程
文档明确要求:遍历 synchronizedSet 时,需在外部用同一把锁同步整个迭代块。推荐写法:
Set<String> syncSet = Collections.synchronizedSet(new HashSet<>());
// ...
synchronized (syncSet) {
for (String s : syncSet) {
// 安全遍历
}
}
若锁对象不是 syncSet 本身(比如构造时传入了自定义锁),则必须用该锁对象同步。
立即学习“Java免费学习笔记(深入)”;
相比 CopyOnWriteArraySet 的适用场景差异
synchronizedSet 适合读多写少、且写操作频率可控的场景;而 CopyOnWriteArraySet 适用于迭代远多于修改、且能接受写操作开销和弱一致性(迭代期间看不到新添加元素)的场景。注意:
- synchronizedSet 支持 null 元素(取决于底层实现,如 HashSet 允许,TreeSet 不允许)
- CopyOnWriteArraySet 不允许 null,且每次 add/remove 都复制数组,内存与时间成本更高
- 若需高频复合操作(如 put-if-absent + 计数),建议直接使用 ConcurrentHashMap 或 java.util.concurrent 中更专用的类型(如 ConcurrentSkipListSet)
替代方案更推荐现代并发集合
除非维护旧代码或有特殊兼容要求,新项目中应优先考虑:
-
ConcurrentHashMap.newKeySet()(JDK 8+):高性能、无锁读、支持高并发写,返回的 Set 是真正并发安全的 -
ConcurrentSkipListSet:支持排序、并发安全,适用于需要有序语义的场景 - 避免在高并发下依赖 synchronizedSet,尤其当存在复合逻辑或频繁迭代时,容易引入隐蔽 bug


















