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"); —— 判断和添加之间存在时间窗口,两个线程都通过判断后执行 addremoveIf、retainAll 等批量操作也不具备原子性正确做法是用该同步 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 块内Collections.synchronizedSet 是全局独占锁,吞吐量低。如果业务读多写少、或并发压力大,优先考虑:
ConcurrentHashMap.newKeySet()(Java 8+):底层基于分段锁/CAS,支持无锁读、并发写,性能显著更好ConcurrentSkipListSet:有序、并发安全,适合需要排序的场景newKeySet() 迭代器不保证强一致性(可能漏元素或看到旧值),但绝大多数业务可接受