collections.synchronizedset通过装饰器模式为set添加synchronized同步块实现线程安全,但仅保障单个方法原子性,复合操作和迭代需手动加锁,高并发下性能受限,读多写少场景可选copyonwritearrayset或concurrenthashmap.keyset()。

Collections.synchronizedSet 是 Java 中让普通 Set 快速具备基础线程安全能力的常用手段,但它不是“开箱即用”的万能方案——单个 add、remove 操作是安全的,但复合操作和迭代仍需额外处理,否则极易出错。
它怎么保证线程安全
本质是装饰器模式:把传入的原始 Set(比如 HashSet)包装成一个新对象,所有 public 方法(add、remove、contains 等)内部都加了 synchronized 同步块,锁对象默认是包装后的集合实例本身(即 this)。这意味着同一时间只有一个线程能执行这些方法,避免了并发修改导致的数据错乱或结构损坏。
- 底层锁粒度较粗,所有操作共用一把锁,高并发下可能成为性能瓶颈
- 它不改变原 Set 的行为,只是加了一层同步外壳
- 适用于读写频率不高、逻辑简单的共享场景
迭代时必须手动加锁
这是最常踩的坑。synchronizedSet 仅保证单个方法调用的安全,而迭代器(Iterator)本身不是线程安全的——即使集合被包装过,遍历时仍可能触发 ConcurrentModificationException。
- 正确做法:用 synchronized 块包裹整个 for-each 或 iterator 遍历过程
- 错误写法:
for (String s : syncSet) { ... }—— 表面简洁,实际未同步,多线程下危险 - 示例:
synchronized (syncSet) { for (String s : syncSet) { System.out.println(s); } }
复合操作不能依赖自动同步
像“先检查再添加”这类两步操作(如 if (!set.contains(x)) set.add(x)),即使用了 synchronizedSet,中间也可能被其他线程插入,导致逻辑失效。
- 原因:contains 和 add 是两个独立的同步方法,之间存在时间窗口
- 解决方式:将整个复合逻辑包进同一个 synchronized 块,锁对象仍是该 set 实例
- 例如:
synchronized (syncSet) { if (!syncSet.contains("key")) { syncSet.add("key"); } }
替代方案要考虑实际负载
如果业务中读多写少,或并发量大、对吞吐要求高,Collections.synchronizedSet 的全局锁就显得笨重。此时可考虑更专业的并发集合:
- CopyOnWriteArraySet:适合读极多、写极少的场景(如监听器列表),每次写操作复制数组,读完全无锁
- ConcurrentHashMap + keySet():虽然没有直接的 ConcurrentHashSet,但可用 ConcurrentHashMap 的 keySet() 视图模拟,支持更高并发度
- 注意:不要为了“线程安全”而盲目替换——低并发简单场景,synchronizedSet 依然够用且清晰
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











