Collections.synchronizedSet仅保证单个方法调用原子性,不保障复合操作和迭代安全;需手动同步遍历,适用读多写少场景;高并发应优先选用ConcurrentHashMap.newKeySet()或ConcurrentSkipListSet。

Collections.synchronizedSet 提供的是一个基础但有明确边界的线程安全方案——它只保单个方法调用的原子性,不保复合逻辑和迭代过程的安全。直接用它“就能线程安全”是个常见误解,关键得知道它在哪有效、在哪失效。
单个操作自动同步,但仅限于 add/remove/contains 等基础方法
包装后的 Set 所有公开方法(如 add、remove、size、contains)内部都加了 synchronized,锁对象默认是该包装实例本身。这意味着两个线程不会同时执行 set.add(x),也不会同时执行 set.remove(y)。这种保护对简单增删查足够,但无法覆盖业务中常见的“先检查再操作”逻辑:
- if (!set.contains("key")) set.add("key"); —— 这两步不是原子的,可能两个线程都通过检查后重复添加
- set.size() == 0 之后执行 set.clear() —— 中间可能有其他线程插入元素,导致语义错误
迭代必须手动加锁,否则容易出错
for-each 或 iterator 遍历不是线程安全的,即使集合本身被 synchronizedSet 包装。不加锁遍历时,可能抛 ConcurrentModificationException,也可能读到不一致的中间状态(比如部分新增元素可见、部分不可见)。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 正确写法:用同一把锁包裹整个遍历块,锁对象必须是 synchronizedSet 实例本身(除非构造时指定了自定义 mutex)
- 示例:synchronized(syncSet) { for (String s : syncSet) { ... } }
- 不能只锁 iterator.next(),也不能在循环体内释放锁
适用场景有限,高并发或复杂逻辑时不推荐
它适合读多写少、写操作频率低、且基本不涉及复合判断或频繁遍历的老系统迁移场景。一旦出现以下情况,就该考虑替代方案:
立即学习“Java免费学习笔记(深入)”;
- 需要 put-if-absent + 计数等组合操作 → 改用 ConcurrentHashMap.newKeySet()
- 迭代远多于修改,且能接受弱一致性(遍历时看不到最新写入)→ 可选 CopyOnWriteArraySet
- 要求高吞吐、低延迟、支持并发写 → 推荐 ConcurrentSkipListSet 或 newKeySet()
替代方案更值得优先考虑
除非维护遗留代码,新项目中应避开 synchronizedSet。JDK 8+ 的 ConcurrentHashMap.newKeySet() 是更优解:无锁读、分段写、支持高并发,且返回的 Set 天然支持安全迭代,无需额外同步。ConcurrentSkipListSet 则提供有序性与并发安全兼顾的能力。两者都不需要开发者操心锁粒度或迭代陷阱。

















