HashSet非线程安全,多线程并发修改会引发ConcurrentModificationException或数据异常;推荐使用ConcurrentHashMap.newKeySet()实现高性能线程安全,读写并发高、size实时准确,但不支持null。

HashSet 本身不是线程安全的,多线程并发修改(比如同时 add、remove 或遍历)会直接导致 ConcurrentModificationException,甚至出现数据丢失、无限循环等不可预知行为。根本原因在于它的迭代器是“快速失败”(fail-fast)机制,且内部没有同步控制。
用 Collections.synchronizedSet 包装
这是最简单的线程安全包装方式,底层通过 synchronized 方法加锁,保证每次操作原子性:
- 创建时:`Set
set = Collections.synchronizedSet(new HashSet());` - 注意:**迭代时仍需手动同步**,否则可能抛出 ConcurrentModificationException
- 正确遍历写法:synchronized(set) { for (String s : set) { ... } }
- 适合读多写少、竞争不激烈的场景;高并发下性能较差,因为所有操作串行化
改用 CopyOnWriteArraySet
适用于**读远多于写、集合规模不大、允许短暂数据延迟**的场景:
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
- 每次写操作(add/remove)都会复制整个底层数组,新引用替换旧引用
- 迭代器基于快照,不会抛 ConcurrentModificationException,也不需要额外同步
- 缺点:内存开销大、写操作成本高(O(n))、无法保证实时一致性(如 size() 可能滞后)
- 典型用途:监听器列表、配置白名单等变更极少的集合
用 ConcurrentHashMap 的 keySet 视图(推荐)
Java 8+ 提供了高性能、真正并发安全的替代方案:
立即学习“Java免费学习笔记(深入)”;
- `Set
set = ConcurrentHashMap.newKeySet();` —— 直接创建线程安全的 Set - 底层基于分段锁或 CAS,支持高并发读写,无迭代异常,size() 实时准确
- 不支持 null 元素(和 ConcurrentHashMap 一致),但绝大多数业务场景够用
- 比 synchronizedSet 吞吐量高,比 CopyOnWriteArraySet 内存更友好
手动加锁(细粒度控制)
当需要组合多个操作(如“检查不存在再添加”)且要求强一致性时:
- 使用 ReentrantLock 或 synchronized 块保护整个逻辑块
- 例如:synchronized(lockObj) { if (!set.contains(x)) set.add(x); }
- 避免锁住整个 HashSet 实例(易引发死锁或扩展性差),建议用独立锁对象
- 慎用:增加复杂度,可能成为性能瓶颈

















