You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java HashSet出现重复值:并行流操作下集合大小异常

问题原因分析与解决方案

核心问题:非线程安全集合的并发修改导致内部结构损坏

你遇到的问题本质是在多线程环境下使用了非线程安全的HashSet,并发执行add操作破坏了集合的内部结构,进而导致size()返回错误值,甚至出现逻辑上不可能的重复元素计数。

具体原因拆解

  1. HashSet的底层实现:HashSet基于HashMap实现,内部维护了size字段记录元素数量,以及哈希桶(链表/红黑树)存储元素。
  2. 并发修改的危害:当多个线程同时调用add时,会同时修改HashMap的内部结构:
    • 可能导致size字段被多线程重复累加,出现数值远大于实际元素数的情况;
    • 可能破坏哈希桶的链表/红黑树结构,导致遍历集合时重复统计元素,或者size字段与实际元素数完全脱节;
    • 你看到的输出里元素都是0-9,但size显示26、16,就是因为底层结构损坏后,size计数和实际存储的元素结构完全不符。
  3. sleep的作用:Thread.sleep(1)只是增加了线程切换的频率,让并发修改的冲突更容易触发;没有sleep时冲突概率降低,但只要存在并发修改,问题依然会偶发。

错误的ChatGPT回复原因

ChatGPT仅从逻辑上判断元素取值范围是0-9,忽略了非线程安全集合在并发场景下的底层结构损坏问题,因此得出了错误结论。

解决方案

按推荐度排序,有以下几种可行修复方式:

  • 使用线程安全的集合:替换HashSet为ConcurrentHashMap.newKeySet(),这是专为并发场景设计的线程安全集合,性能优于同步包装类:
    Set<Integer> usedIndexes = ConcurrentHashMap.newKeySet();
    
  • 用同步包装类:通过Collections.synchronizedSet包装HashSet,给所有操作加上同步锁:
    Set<Integer> usedIndexes = Collections.synchronizedSet(new HashSet<>());
    
  • 使用并行流的collect操作:放弃forEach,改用collect收集结果,并行流的collect是线程安全的,会自动处理并发收集:
    Set<Integer> usedIndexes = Stream.generate(() -> index.getAndIncrement() % totalIndexes)
            .parallel()
            .limit(1000)
            .collect(Collectors.toSet());
    

内容的提问来源于stack exchange,提问作者Eugene Marin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 07:15:31