Java多线程执行多于循环边界

时间:2015-05-29 13:25:45

标签: java multithreading executorservice threadpoolexecutor

我正在创建一个网络抓取工具,用于从网络中提取链接和电子邮件。链接将用于查找新地点以搜索电子邮件,然后电子邮件将存储在一组中。每个链接都传递到自己的线程中的固定线程池,以查找更多电子邮件。我从小开始只寻找10封电子邮件但由于某种原因,我的代码返回了大约13封电子邮件。

    while (emailSet.size() <= EMAIL_MAX_COUNT) {

        link = linksToVisit.poll();

        linksToVisit.remove(link);
        linksVisited.add(link);
        pool.execute(new Scraper(link));
    }

    pool.shutdownNow();

    emailSet.stream().forEach((s) -> {
        System.out.println(s);
    });
    System.out.println(emailSet.size());

虽然我知道有可能创建额外的线程,在我收到10封电子邮件之后仍然会运行但不应该pool.shutdownNow()结束这些线程吗?

如果有帮助,这是我的线程代码。

class Scraper implements Runnable {

    private String link;

    Scraper(String s) {
        link = s;
    }

    @Override
    public void run() {
        try {
            Document doc = (Document) Jsoup.connect(link).get();

            Elements links = doc.select("a[href]");
            for (Element href : links) {
                String newLink = href.attr("abs:href");
                if (!linksVisited.contains(newLink) && !linksToVisit.contains(newLink)) {
                    linksToVisit.add(newLink);
                }
            }
            Pattern p = Pattern.compile(
                    "[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+");
            Matcher matcher = p.matcher(doc.text());
            while (matcher.find()) {
                emailSet.add(matcher.group());
            }
        } catch (Exception e) {
            //Catch on of the many exceptions Jsoup.connect might throw 
            // and just let the thread expire.
        }
    }
}

编辑1:

我应该第一次包含这个,但我使用的是线程安全集和队列。

Set<String> emailSet = Collections.synchronizedSet(new HashSet());
BlockingQueue<String> linksToVisit = new ArrayBlockingQueue(10000);
Set<String> linksVisited = Collections.synchronizedSet(new HashSet());
final int EMAIL_MAX_COUNT = 10;
ExecutorService pool = newFixedThreadPool(25);

编辑2

想到我应该用答案更新我的问题,所以这就是我的问题所在。

    while (emailSet.size() <= EMAIL_MAX_COUNT) {

    link = linksToVisit.poll();

    linksToVisit.remove(link);
    linksVisited.add(link);
    pool.execute(new Scraper(link));
}

我的列表只从一个链接开始。删除第一个链接后,我有一个空列表,不断创建新的线程,没有链接搜索。在列表可以填充之前,我已经创建了数百个线程,只是放慢系统速度直到它最终崩溃。

以下是代码修复,以确保在没有搜索链接的情况下不会创建任何线程。

while (emailSet.size() <= EMAIL_MAX_COUNT) {

        if (linksToVisit.size() > 0) {
            link = linksToVisit.poll();

            linksToVisit.remove(link);
            linksVisited.add(link);
            pool.execute(new Scraper(link));
            //System.out.println("Emails " + emailSet.size());
        } else {
            try {
                Thread.sleep(100);
            } catch (InterruptedException ex) {
                Logger.getLogger(Crawler.class.getName())
                        .log(Level.SEVERE, null, ex);
            }
        }
    }

1 个答案:

答案 0 :(得分:1)

你在一个检查emailSet大小的循环中以异步方式启动scaper,但是在循环的一个循环期间,一个scraper可以找到多个电子邮件,或者你可以启动多个scraper并在你启动之后它添加了alla电子邮件链接页面 考虑以下时间

T1 loop start ->T2 loop schedule Scaper ->T3 loop check emailSet ->T4 Scraper finds 13 email -> T5  loop check emailSet

或以下

T1 loop start ->T2 loop schedule Scaper "1" ->T3 loop check emailSet ->T4 loop schedule Scaper "2" T5 -> Scraper "1" finds 6 emails -> T6  loop check emailSet -> Scraper "1" finds 7 emails

等等。

如果您想在发现10封电子邮件时停止,则必须更改以下内容

while (matcher.find()) {
    emailSet.add(matcher.group());
}

while (matcher.find()) {
    if (emailSet.size() <= EMAIL_MAX_COUNT) {
        emailSet.add(matcher.group());
    }
}

甚至这并不能完全保证您可以在EMAIL_MAX_COUNT处停止,因为有多个线程(例如3个)可以检查大小并获得9然后所有它们都插入一封电子邮件。

如果要确保精确的emailSet大小,必须在单个块(使用synchronized(emailSet)或使用Lock)内同步读写操作;

之类的东西
while (matcher.find()) {
    synchronized(emailSet) {
       if (emailSet.size() <= EMAIL_MAX_COUNT) {
           emailSet.add(matcher.group());
       }
    }
}