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

使用ExecutorService存WebPage到Set却为空,求排查问题

多线程下HashSet添加元素为空的问题分析与解决

兄弟,你的问题主要出在两个核心点,再加上一个容易忽略的细节,我给你逐一拆解:

1. HashSet本身不是线程安全的集合

HashSet的底层是基于HashMap实现的,而HashMap和HashSet都没有任何同步机制。当多个线程同时调用webPages.add()的时候,会出现竞态条件:

  • 比如两个线程同时检查某个元素是否存在(基于hashCode和equals),然后都尝试添加,可能导致元素被覆盖或者根本没被正确插入;
  • 更严重的是,并发修改HashSet内部的哈希表结构时,可能会导致链表成环、扩容异常等问题,直接让集合处于不一致状态,元素无法正常存储。

2. 主线程没有等待线程池任务全部完成

你调用了executor.shutdown(),但这个方法只是告诉线程池“不再接受新任务”,已经提交的任务可能还在后台执行。如果主线程在shutdown()之后直接结束,那么这些子线程可能还没来得及执行webPages.add()操作,你去查看集合的时候自然是空的。

3. 容易忽略的细节:WebPage类的equals和hashCode实现

HashSet是通过元素的hashCode()和equals()来判断是否重复的。如果你的WebPage类没有正确重写这两个方法,就算元素被添加进去,也可能因为HashSet认为是重复元素(或者错误地认为是不同元素但存储异常),导致最终集合不符合预期。


对应的解决办法

(1)替换为线程安全的集合

把HashSet换成以下两种线程安全的集合之一:

  • Collections.synchronizedSet()包装的HashSet:给HashSet加上同步锁,保证每次只有一个线程能操作集合:
    Set<WebPage> webPages = Collections.synchronizedSet(new HashSet<>());
    
  • CopyOnWriteArraySet:适合读多写少的场景,每次写入都会复制一个新的数组,避免并发修改问题:
    Set<WebPage> webPages = new CopyOnWriteArraySet<>();
    

(2)等待线程池任务执行完毕

在executor.shutdown()之后,调用awaitTermination()等待所有任务完成,确保主线程会等到所有子线程都执行完添加操作:

executor.shutdown();
try {
    // 设置一个合理的超时时间,比如5分钟
    if (!executor.awaitTermination(5, TimeUnit.MINUTES)) {
        // 如果超时就强制关闭线程池
        executor.shutdownNow();
    }
} catch (InterruptedException e) {
    executor.shutdownNow();
    // 恢复中断状态
    Thread.currentThread().interrupt();
}

(3)确保WebPage正确重写equals和hashCode

比如根据title、link这些核心字段来实现:

import java.util.Objects;

class WebPage {
    private String title;
    private String link;
    private String bodyText;

    public WebPage(String title, String link, String bodyText) {
        this.title = title;
        this.link = link;
        this.bodyText = bodyText;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        WebPage webPage = (WebPage) o;
        return Objects.equals(title, webPage.title) &&
               Objects.equals(link, webPage.link) &&
               Objects.equals(bodyText, webPage.bodyText);
    }

    @Override
    public int hashCode() {
        return Objects.hash(title, link, bodyText);
    }

    // 按需添加getter、setter方法
}

修改后的完整示例代码

public void spinOffThreadsToGetPages() {
    // 使用线程安全的集合
    Set<WebPage> webPages = Collections.synchronizedSet(new HashSet<>());
    ExecutorService executor = Executors.newFixedThreadPool(10);
    
    for (String link : links) {
        executor.submit(() -> addWebPagesToSet(webPages, link));
    }
    
    executor.shutdown();
    // 等待所有任务完成
    try {
        if (!executor.awaitTermination(5, TimeUnit.MINUTES)) {
            executor.shutdownNow();
        }
    } catch (InterruptedException e) {
        executor.shutdownNow();
        Thread.currentThread().interrupt();
    }
    
    // 此时webPages应该包含所有成功获取的WebPage对象
}

private void addWebPagesToSet(Set<WebPage> webPages, String link) {
    Document document = getDocument(link);
    if (document == null) {
        return;
    }
    WebPage webPage = new WebPage(document.title(), link, document.body().text());
    webPages.add(webPage);
}

// 正确实现equals和hashCode的WebPage类
class WebPage {
    private String title;
    private String link;
    private String bodyText;

    public WebPage(String title, String link, String bodyText) {
        this.title = title;
        this.link = link;
        this.bodyText = bodyText;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        WebPage webPage = (WebPage) o;
        return Objects.equals(title, webPage.title) &&
               Objects.equals(link, webPage.link) &&
               Objects.equals(bodyText, webPage.bodyText);
    }

    @Override
    public int hashCode() {
        return Objects.hash(title, link, bodyText);
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.09 21:42:55