簡體   English   中英

如何檢查ConcurrentLinkedQueue的size()或isEmpty()

[英]How to check the size() or isEmpty() for ConcurrentLinkedQueue

我正在嘗試為Java中的Web爬網程序設計簡單的結構原型。 到目前為止,原型僅嘗試執行以下操作:

  • 使用起始URL列表初始化隊列
  • 從隊列中取出一個URL並提交到新線程
  • 做一些工作,然后將該URL添加到一組已訪問的URL中

對於起始URL隊列,我正在使用ConcurrentLinkedQueue進行同步。 要生成新線程,我正在使用ExecutorService

但是在創建新線程時,應用程序需要檢查ConcurrentLinkedQueue是否為空。 我嘗試使用:

  • .size()
  • .isEmpty()

但是兩者似乎都沒有返回ConcurrentLinkedQueue的真實狀態。

問題在下面的塊中:

while (!crawler.getUrl_horizon().isEmpty()) {
                workers.submitNewWorkerThread(crawler);
            }

因此,即使輸入只有2個URL,ExecutorService也會在其限制內創建所有線程。

這里實現多線程的方式是否有問題? 如果不是,那么檢查ConcurrentLinkedQueue狀態的更好方法是什么?

該應用程序的入門班:

public class CrawlerApp {

    private static Crawler crawler;

    public static void main(String[] args) {
        crawler = = new Crawler();
        initializeApp();
        startCrawling();

    }

    private static void startCrawling() {
        crawler.setUrl_visited(new HashSet<URL>());
        WorkerManager workers = WorkerManager.getInstance();
        while (!crawler.getUrl_horizon().isEmpty()) {
            workers.submitNewWorkerThread(crawler);
        }
        try {
            workers.getExecutor().shutdown();
            workers.getExecutor().awaitTermination(10, TimeUnit.MINUTES);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private static void initializeApp() {

        Properties config = new Properties();
        try {
            config.load(CrawlerApp.class.getClassLoader().getResourceAsStream("url-horizon.properties"));
            String[] horizon = config.getProperty("urls").split(",");
            ConcurrentLinkedQueue<URL> url_horizon = new ConcurrentLinkedQueue<>();
            for (String link : horizon) {
                URL url = new URL();
                url.setURL(link);
                url_horizon.add(url);
            }
            crawler.setUrl_horizon(url_horizon);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

}

Crawler.java ,用於維護URL隊列和已訪問URL的集合。

public class Crawler implements Runnable {
    private ConcurrentLinkedQueue<URL> url_horizon;

    public void setUrl_horizon(ConcurrentLinkedQueue<URL> url_horizon) {
        this.url_horizon = url_horizon;
    }

    public ConcurrentLinkedQueue<URL> getUrl_horizon() {
        return url_horizon;
    }

    private Set<URL> url_visited;

    public void setUrl_visited(Set<URL> url_visited) {
        this.url_visited = url_visited;
    }

    public Set<URL> getUrl_visited() {
        return Collections.synchronizedSet(url_visited);
    }

    @Override
    public void run() {
        URL url = nextURLFromHorizon();
        scrap(url);
        addURLToVisited(url);

    }

    private URL nextURLFromHorizon() {
        if (!getUrl_horizon().isEmpty()) {
            URL url = url_horizon.poll();
            if (getUrl_visited().contains(url)) {
                return nextURLFromHorizon();
            }
            System.out.println("Horizon URL:" + url.getURL());
            return url;

        }
        return null;

    }

    private void scrap(URL url) {
        new Scrapper().scrap(url);
    }

    private void addURLToVisited(URL url) {
        System.out.println("Adding to visited set:" + url.getURL());
        getUrl_visited().add(url);
    }

}

URL.java只是一個具有private String url並覆蓋了hashCode()equals()

此外, Scrapper.scrap()到目前為止還具有虛擬實現:

public void scrap(URL url){
        System.out.println("Done scrapping:"+url.getURL());
    }

WorkerManager創建線程:

public class WorkerManager {
    private static final Integer WORKER_LIMIT = 10;
    private final ExecutorService executor = Executors.newFixedThreadPool(WORKER_LIMIT);

    public ExecutorService getExecutor() {
        return executor;
    }

    private static volatile WorkerManager instance = null;

    private WorkerManager() {
    }

    public static WorkerManager getInstance() {
        if (instance == null) {
            synchronized (WorkerManager.class) {
                if (instance == null) {
                    instance = new WorkerManager();
                }
            }
        }

        return instance;
    }

    public Future submitNewWorkerThread(Runnable run) {
        return executor.submit(run);
    }

}

問題

之所以最終創建的線程比隊列中的URL多​​,是因為(很可能)Executor的所有Thread都沒有啟動,直到您多次經過while循環為止。

每當使用線程時,都應牢記線程是獨立調度的,並以其自己的速度運行,除非您明確同步它們。 在這種情況下,線程可以在之后的任何時間開始submit()調用,即使它看起來你想每一個開始,現在已經超過nextURLFromHorizon在你的下一次迭代之前while循環。

在將Runnable提交給執行程序之前,請考慮從隊列中使URL出隊。 我還建議定義一個CrawlerTask ,該CrawlerTask一次提交給執行器,而不是重復提交的Crawler 在這種設計中,您甚至不需要線程安全的容器即可抓取URL。

class CrawlerTask extends Runnable {
   URL url;

   CrawlerTask(URL url) { this.url = url; }

   @Override
   public void run() {
     scrape(url);
     // add url to visited?
   }
}

class Crawler {
  ExecutorService executor;
  Queue urlHorizon;

  //...

  private static void startCrawling() {
    while (!urlHorizon.isEmpty()) {
      executor.submit(new CrawlerTask(urlHorizon.poll());
    }
    // ...
  }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM