HashSet非线程安全,多线程下易出错;应选用Collections.synchronizedSet(需手动同步迭代)、CopyOnWriteArraySet(读多写少)或ConcurrentHashMap.keySet(JDK8+推荐)等线程安全方案。

HashSet 本身不是线程安全的,多线程同时调用 add、remove 或遍历操作时,可能出现数据丢失、ConcurrentModificationException,甚至内部结构损坏(如哈希表链表成环)。要保证线程安全,不能靠“手动加锁”或“自己同步”,而应选用经过充分验证的线程安全方案。
使用 Collections.synchronizedSet 包装
这是最直接的兼容性方案,适用于已有 HashSet 实例、改动范围小的场景:
- 调用
Collections.synchronizedSet(new HashSet())返回一个线程安全的 Set 包装器 - 所有方法(
add、contains、size等)都自动加同一把对象锁,保证串行执行 - 注意:迭代仍需手动同步,否则可能抛出
ConcurrentModificationException - 示例:
Set<String> safeSet = Collections.synchronizedSet(new HashSet<>());<br>new Thread(() -> {<br> synchronized (safeSet) {<br> for (String s : safeSet) { /* 安全遍历 */ }<br> }<br>}).start();
使用 CopyOnWriteArraySet
适合读多写少、允许短暂最终一致性的场景(如监听器注册、配置白名单):
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 底层基于
CopyOnWriteArrayList实现,每次写操作(add/remove)都会复制整个数组 - 读操作完全无锁、高性能;写操作开销大,不适合高频修改
- 迭代器不抛
ConcurrentModificationException,且看到的是快照,不会受其他线程写入影响 - 不支持
null元素(与 HashSet 一致)
使用 ConcurrentHashMap 的 keySet 视图
这是 JDK 8+ 推荐的高性能替代方案,兼顾并发能力和语义等价性:
-
ConcurrentHashMap<String, Boolean> map = new ConcurrentHashMap<>();
再通过map.keySet()获取一个线程安全的 Set 视图 - 支持高并发读写,分段锁/CAS + synchronized 优化,吞吐量远高于 synchronizedSet
- 写操作原子、可见;遍历时不会因其他线程修改而失败
- 注意:该 Set 是“实时视图”,不是独立集合,其行为与 ConcurrentHashMap 保持一致
避免常见误区
以下做法不可取或效果有限:
- 在业务代码里对每个
add手动synchronized(this)—— 锁粒度粗、易死锁、无法覆盖迭代逻辑 - 用
TreeSet替代 —— 它也不线程安全,且排序开销更大 - 认为“只读就不需要处理” —— 若有其他线程正在写,未同步的读仍可能看到不一致状态(内存可见性问题)
- 依赖
volatile Set<?>—— volatile 只能保证引用本身可见,不能保证集合内部操作线程安全

















