一聚教程网:一个值得你收藏的教程网站

热门教程

Java集合并发安全:Collections.synchronizedSet实战应用

时间:2026-06-25 08:21:46 编辑:袖梨 来源:一聚教程网

Collections.synchronizedSet()仅保证单个方法原子性,复合操作和迭代需手动同步;正确做法是以该Set实例为锁对象加synchronized块,遍历时也需同步或转数组;高并发场景推荐ConcurrentHashMap.newKeySet()或ConcurrentSkipListSet。

直接用 Collections.synchronizedSet(new HashSet()) 能让单个 add、remove、contains 操作线程安全,但复合逻辑和迭代仍会出错——关键不在“包没包好”,而在“怎么用才不出问题”。

单方法安全,不代表整体安全

包装后的 Set 确保每个 public 方法(如 add()contains())是原子的,但多个方法组合就不受保护。比如下面这段代码在多线程下可能重复添加:

  • if (!set.contains("x")) set.add("x"); —— 判断和添加之间存在时间窗口,两个线程都通过判断后执行 add
  • 类似地,removeIfretainAll 等批量操作也不具备原子性

复合操作必须加同步块

正确做法是用该同步 Set 实例本身作锁对象,不要另建新锁:

  • ✅ 正确:synchronized (syncSet) { if (!syncSet.contains("key")) syncSet.add("key"); }
  • ❌ 错误:synchronized (new Object()) { ... }synchronized (this) { ... } —— 锁对象不一致,不同步
  • 注意:syncSet 内部的 mutex 字段不可见,但其自身就是锁对象,这是 Collections.synchronizedSet 的设计约定

遍历必须手动同步,否则抛异常

即使 Set 是同步的,它的迭代器不是线程安全的。并发修改+遍历大概率触发 ConcurrentModificationException

立即学习“Java免费学习笔记(深入)”;

  • ✅ 安全遍历:synchronized (syncSet) { for (String s : syncSet) { ... } }
  • ✅ 替代方案(读多写少且数据量小):for (String s : syncSet.toArray()) { ... }
  • ❌ 危险操作:边遍历边调用 remove()add(),除非整个操作都在同一 synchronized 块内

高并发场景建议换 ConcurrentSet

Collections.synchronizedSet 是全局独占锁,吞吐量低。如果业务读多写少、或并发压力大,优先考虑:

  • ConcurrentHashMap.newKeySet()(Java 8+):底层基于分段锁/CAS,支持无锁读、并发写,性能显著更好
  • ConcurrentSkipListSet:有序、并发安全,适合需要排序的场景
  • 注意:newKeySet() 迭代器不保证强一致性(可能漏元素或看到旧值),但绝大多数业务可接受

热门栏目