hashset非线程安全,多线程读写会导致异常与数据损坏;推荐使用concurrenthashmap.newkeyset()(jdk8+),其高性能、弱一致性迭代器且操作原子;次选copyonwritearrayset(读多写少)或collections.synchronizedset(低并发适配),须规避手动加锁、treeset替代等错误做法。

HashSet 本身不是线程安全的,多线程同时读写会导致 ConcurrentModificationException、数据丢失,甚至内部哈希表结构损坏(比如链表成环)。不能靠手动加锁或只同步部分操作来“凑合”,必须选用经过验证的并发集合方案。
用 ConcurrentHashMap.newKeySet()(JDK 8+ 推荐)
这是当前最平衡的选择:高性能、高并发、语义接近 HashSet。
- 底层基于
ConcurrentHashMap,读写都支持细粒度并发,吞吐量远高于全局锁方案 - 调用
ConcurrentHashMap.newKeySet()直接获得线程安全的Set视图,无需包装 - 所有基础操作(
add、remove、contains)天然原子且线程安全 - 迭代器是弱一致性的——不抛异常,可能看不到最新写入,但不会出错或阻塞
- 注意:不支持
null元素;不是SortedSet;默认不可序列化
读多写少时选 CopyOnWriteArraySet
适合监听器列表、配置白名单等场景,强调遍历绝对安全、无锁。
- 每次写操作(
add、remove)都会复制整个数组,读操作永远基于某个快照 - for-each 或 iterator 遍历完全不需要同步,也不会抛
ConcurrentModificationException - 写开销大,不适合高频修改;内存占用随写操作增长;同样不支持
null
兼容旧代码可选 Collections.synchronizedSet()
适合改动小、并发压力低、需快速适配已有 HashSet 的情况。
- 用
Collections.synchronizedSet(new HashSet())包装,所有单方法调用自动同步 - ⚠️ 迭代必须手动加锁:
synchronized(set) { for (e : set) { ... } } - ⚠️ 复合逻辑(如“不存在才添加”)也必须整个包裹在同步块里
- 缺点明显:粗粒度锁,所有操作竞争同一把锁,高并发下易成瓶颈
哪些做法要避免
这些看似“能用”,实则隐患大、效果差或根本无效:
- 对原始
HashSet手动synchronized(this)—— 锁对象难统一,易漏、易死锁,覆盖不了迭代 - 用
TreeSet替代 —— 它也不线程安全,且排序带来额外开销 - 认为“只读就不用管” —— 未同步的读可能看到过期值(可见性问题)
- 给 Set 引用加
volatile—— 只保证引用更新可见,不保护集合内部状态
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











