hashset不是线程安全的,因其无同步机制、迭代器快速失败且存在可见性问题;推荐用concurrenthashmap.newkeyset()、collections.synchronizedset()或copyonwritearrayset替代。

HashSet 本身不是线程安全的,多线程并发修改会直接导致异常或数据不一致。 它底层基于 HashMap 实现,而 HashMap 在并发 put/remove 时可能触发扩容、链表转红黑树等操作,多个线程同时修改容易引发死循环(JDK 7)、数组越界、元素丢失或 ConcurrentModificationException(迭代期间被修改)。
为什么不能直接在多线程中用 HashSet
HashSet 没有任何内部同步机制:
- 添加、删除、查找操作都不加锁;
- 迭代器是“快速失败”(fail-fast)的,一旦检测到结构被其他线程修改,立刻抛
ConcurrentModificationException; - 即使只读操作,在写操作未完成时也可能看到中间状态(如部分扩容后的数组),存在可见性问题。
安全替代方案(按场景推荐)
不要自己加 synchronized 包裹 HashSet 操作——粒度粗、性能差、仍可能漏掉迭代场景。 推荐以下方式:
- 用
Collections.synchronizedSet(new HashSet())
- 返回一个包装过的线程安全 Set,所有方法加了对象锁;
- ⚠️ 注意:迭代仍需手动同步,否则可能抛异常:
Set<string> s = Collections.synchronizedSet(new HashSet());<br> synchronized (s) {<br> for (String e : s) { /* 安全 */ }<br> }</string> - 用
ConcurrentHashMap.newKeySet() - JDK 8+ 提供,底层基于 ConcurrentHashMap,支持高并发读写;
- 无锁读、分段写,性能远优于 synchronizedSet;
- 迭代器是弱一致性(weakly consistent),不会抛
ConcurrentModificationException,可安全遍历。 - 用
CopyOnWriteArraySet - 适合读多写少(如监听器列表);
- 每次写操作都复制整个数组,写代价高,大数据量时不推荐;
- 迭代无需加锁,绝对安全,但看到的可能是旧快照。
实际选择建议
根据读写比例和实时性要求选:
- 通用高并发场景 → 优先用
ConcurrentHashMap.newKeySet(); - 简单同步需求、代码兼容老 JDK → 用
Collections.synchronizedSet,并注意迭代同步; - 写极少、读极多且能接受旧数据 →
CopyOnWriteArraySet; - 需要强一致性 + 复杂逻辑 → 考虑显式加锁(如
ReentrantLock)或使用并发容器组合。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











