Collections.synchronizedSet()仅保证单方法原子性,复合操作如“检查后添加”仍需手动同步;正确做法是用该集合实例作为锁对象,或改用ConcurrentHashMap.newKeySet()提升并发性能。

为什么不能直接用 Collections.synchronizedSet(new HashSet()) 就完事?
它确实能返回一个线程安全的 Set,但「线程安全」只覆盖单个方法调用——比如 add()、contains() 是原子的;一旦涉及复合操作(如“检查不存在再添加”),仍需手动同步。常见误用是写 if (!set.contains(x)) set.add(x);,这在多线程下会重复添加。
正确包装并确保复合操作安全的写法
必须配合外部 synchronized 块,锁对象得用包装后集合的 mutex 字段(Collections.synchronizedSet() 内部维护的)。
- 获取 mutex:包装后的集合本身不暴露 mutex,但其内部类持有,所以应直接用该集合实例作为锁对象
- 示例:
Set<String> syncSet = Collections.synchronizedSet(new HashSet<>());<br>// 安全的“若不存在则添加”<br>synchronized (syncSet) {<br> if (!syncSet.contains("key")) {<br> syncSet.add("key");<br> }<br>} - 别用
new Object()或其他任意对象做锁,否则不同步
比 synchronizedSet() 更适合高并发场景的替代方案
Collections.synchronizedSet() 是粗粒度锁,整个集合一把锁,吞吐量低。如果读多写少或要求更高并发性,优先考虑:
当用户要求为 Java 代码添加注释、修改注释、审查注释、整理注释格式,或要求按照代码注释规范处理 Java 文件时,使用本技能。本技能用于统一生成和维护 Java 类注释模板、按 Git 对比 release 判断版本号、补充每行代码上方中文块注释、调整注释空行规则,并保持 Java 文件 UTF-8 无 BOM 编码。典型触发场景包括:补充类注释、更新 @version、检查 @author 来源、整理行尾注释、把注释放到代码上一行、检查注释是否符合规范、构建前检查 Java 注释风格。
-
ConcurrentHashMap.newKeySet()(Java 8+):底层基于ConcurrentHashMap,支持无锁读、分段写,性能好得多 - 示例:
Set<String> concurrentSet = ConcurrentHashMap.newKeySet();<br>// 下面这行是线程安全的,无需额外同步<br>concurrentSet.add("item"); - 注意:
ConcurrentHashMap.newKeySet()不保证迭代器强一致性(可能漏掉或重复元素),但大多数业务场景可接受
容易被忽略的迭代陷阱
即使用了 Collections.synchronizedSet(),遍历仍可能抛 ConcurrentModificationException,因为迭代器本身不是同步的。
立即学习“Java免费学习笔记(深入)”;
- 必须在同步块中迭代:
synchronized (syncSet) {<br> for (String s : syncSet) {<br> // ...<br> }<br>} - 或者转成数组再遍历(适合读多写少且数据量不大):
syncSet.toArray() - 千万别边遍历边修改(如
removeIf()),除非也在同一把锁里

















