Collections.synchronizedSet 返回原始 Set 的同步包装视图,仅保证单个操作线程安全,复合操作和迭代需手动同步,适用于低频读写、逻辑简单场景,不适用于高吞吐或复杂并发逻辑。

Java 中的 Collections.synchronizedSet 是一种轻量级线程安全包装方案,它不改变原始 Set,而是返回一个同步视图——所有单个操作(如 add、remove、contains)自动加锁,但不保证复合操作原子性,也不解决迭代并发问题。
什么时候适合用 synchronizedSet
适用于多线程读写频率不高、逻辑简单、且无需高吞吐的场景:
- 多个线程偶尔向共享 Set 添加或删除少量元素,比如缓存白名单、任务 ID 记录等
- 已有代码基于 HashSet/TreeSet,暂时无法重构为 ConcurrentHashMap 或 CopyOnWriteArraySet
- 仅需基础线程安全,不涉及“检查后插入”“遍历中修改”等复杂逻辑
必须手动同步的两种典型情况
synchronizedSet 只同步每个方法调用本身,以下两类操作仍需显式加锁:
-
迭代遍历:直接 for-each 或 iterator 遍历时,若其他线程同时修改集合,会抛 ConcurrentModificationException。正确写法是:
synchronized (syncSet) { for (String item : syncSet) { // 处理 item } } -
条件性复合操作:例如“如果不存在则添加”,add() 和 contains() 是两个独立调用,中间可能被其他线程插入相同元素。应包裹在同一个同步块中:
synchronized (syncSet) { if (!syncSet.contains(key)) { syncSet.add(key); } }
与原集合的关系和常见误用
返回的同步 Set 是对原始 Set 的包装,二者共享底层数据结构:
立即学习“Java免费学习笔记(深入)”;
- 原始 Set 仍可被直接访问——一旦绕过同步视图修改它,线程安全性立即失效
- 不能将 synchronizedSet 返回的对象赋值给原始变量并继续使用原引用,否则等于“开了后门”
- 不可误认为它是“深不可变”:集合里存的是可变对象时,对象状态仍可被外部修改
更现代的替代选择参考
当出现以下信号时,建议转向 java.util.concurrent 包中的专用类:
- 写操作频繁 → 考虑 ConcurrentHashMap.keySet()(比 synchronizedSet 性能更好)
- 读远多于写,且集合大小稳定 → CopyOnWriteArraySet(迭代绝对安全,但写开销大)
- 需要强一致性语义(如近实时去重+计数)→ 直接用 ConcurrentHashMap 模拟 Set 行为(put(key, Boolean.TRUE))


















