![](/img/trans.png)
[英]ExecutorService that cancels current task when new ones are submitted
[英]ExecutorService never stops. When execute new task inside another executing task
美好的一天。
我的網絡抓取工具有阻塞問題。 邏輯很簡單。 首先創建一個Runnable
,它下載html文檔,掃描所有鏈接,然后在所有資助的鏈接上創建新的Runnable
對象。 每個新創建的Runnable
都會為每個鏈接創建新的Runnable
對象並執行它們。
問題是ExecutorService
永遠不會停止。
CrawlerTest.java
public class CrawlerTest {
public static void main(String[] args) throws InterruptedException {
new CrawlerService().crawlInternetResource("https://jsoup.org/");
}
}
CrawlerService.java
import java.io.IOException;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
public class CrawlerService {
private Set<String> uniqueUrls = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>(10000));
private ExecutorService executorService = Executors.newFixedThreadPool(8);
private String baseDomainUrl;
public void crawlInternetResource(String baseDomainUrl) throws InterruptedException {
this.baseDomainUrl = baseDomainUrl;
System.out.println("Start");
executorService.execute(new Crawler(baseDomainUrl)); //Run first thread and scan main domain page. This thread produce new threads.
executorService.awaitTermination(10, TimeUnit.MINUTES);
System.out.println("End");
}
private class Crawler implements Runnable { // Inner class that encapsulates thread and scan for links
private String urlToCrawl;
public Crawler(String urlToCrawl) {
this.urlToCrawl = urlToCrawl;
}
public void run() {
try {
findAllLinks();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void findAllLinks() throws InterruptedException {
/*Try to add new url in collection, if url is unique adds it to collection,
* scan document and start new thread for finded links*/
if (uniqueUrls.add(urlToCrawl)) {
System.out.println(urlToCrawl);
Document htmlDocument = loadHtmlDocument(urlToCrawl);
Elements findedLinks = htmlDocument.select("a[href]");
for (Element link : findedLinks) {
String absLink = link.attr("abs:href");
if (absLink.contains(baseDomainUrl) && !absLink.contains("#")) { //Check that we are don't go out of domain
executorService.execute(new Crawler(absLink)); //Start new thread for each funded link
}
}
}
}
private Document loadHtmlDocument(String internetResourceUrl) {
Document document = null;
try {
document = Jsoup.connect(internetResourceUrl).ignoreHttpErrors(true).ignoreContentType(true)
.userAgent("Mozilla/5.0 (Windows NT 6.1; WOW64; rv:48.0) Gecko/20100101 Firefox/48.0")
.timeout(10000).get();
} catch (IOException e) {
System.out.println("Page load error");
e.printStackTrace();
}
return document;
}
}
}
此應用程序需要大約20秒來掃描jsoup.org以獲取所有唯一鏈接。 但它只需等待10分鍾executorService.awaitTermination(10, TimeUnit.MINUTES);
然后我看到死主線程仍在工作執行器。
如何強制ExecutorService
正常工作?
我認為問題是它在主線程中調用另一個任務中的executorService.execute。
你是在濫用awaitTermination
。 根據javadoc你應該首先調用shutdown
:
阻止所有任務在關閉請求之后完成執行,或者發生超時,或者當前線程被中斷,以先發生者為准。
為了實現你的目標,我建議使用CountDownLatch
(或支持像這樣的增量的鎖存器)來確定沒有任務的確切時刻,這樣你就可以安全地shutdown
。
我從前面看到你的評論:
我不能使用CountDownLatch,因為我事先不知道我將從資源中收集多少個唯一鏈接。
首先,vsminkov就是為什么awaitTermniation
將等待10分鍾的答案。 我會提供另一種解決方案。
而不是使用CountDownLatch
使用Phaser 。 對於每個新任務,您都可以注冊並等待完成。
創建一個移相器並在每次調用execute.submit
register
並在每次Runnable
完成時arrive
。
public void crawlInternetResource(String baseDomainUrl) {
this.baseDomainUrl = baseDomainUrl;
Phaser phaser = new Phaser();
executorService.execute(new Crawler(phaser, baseDomainUrl));
int phase = phaser.getPhase();
phase.awaitAdvance(phase);
}
private class Crawler implements Runnable {
private final Phaser phaser;
private String urlToCrawl;
public Crawler(Phaser phaser, String urlToCrawl) {
this.urlToCrawl = urlToCrawl;
this.phaser = phaser;
phaser.register(); // register new task
}
public void run(){
...
phaser.arrive(); //may want to surround this in try/finally
}
你不是在叫停機。
這可能有效--CrawlerService中的AtomicLong變量。 在將每個新子任務提交給執行程序服務之前遞增。
修改run()方法以遞減此計數器,如果為0,則關閉執行程序服務
public void run() {
try {
findAllLinks();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//decrements counter
//If 0, shutdown executor from here or just notify CrawlerService who would be doing wait().
}
}
在“finally”中,減少計數器,當計數器為零時,關閉執行程序或只是通知CrawlerService。 0表示,這是最后一個,沒有其他正在運行,沒有在隊列中掛起。 沒有任務會提交任何新的子任務。
如何強制ExecutorService正常工作?
我認為問題是它在主線程中調用另一個任務中的executorService.execute。
不。問題不在於ExecutorService。 您使用的API方式不正確,因此無法獲得正確的結果。
您必須按特定順序使用三個API才能獲得正確的結果。
1. shutdown
2. awaitTermination
3. shutdownNow
從ExecutorService的 oracle文檔頁面推薦的方法:
void shutdownAndAwaitTermination(ExecutorService pool) {
pool.shutdown(); // Disable new tasks from being submitted
try {
// Wait a while for existing tasks to terminate
if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
pool.shutdownNow(); // Cancel currently executing tasks
// Wait a while for tasks to respond to being cancelled
if (!pool.awaitTermination(60, TimeUnit.SECONDS))
System.err.println("Pool did not terminate");
}
} catch (InterruptedException ie) {
// (Re-)Cancel if current thread also interrupted
pool.shutdownNow();
// Preserve interrupt status
Thread.currentThread().interrupt();
}
shutdown():
啟動有序關閉,其中執行先前提交的任務,但不接受任何新任務。
shutdownNow():
嘗試停止所有正在執行的任務,停止等待任務的處理,並返回等待執行的任務列表。
awaitTermination():
阻塞,直到所有任務在關閉請求完成后執行,或發生超時,或者當前線程被中斷,以先發生者為准。
另請注意:如果要等待所有任務完成,請參閱此相關的SE問題:
我更喜歡使用invokeAll()
或ForkJoinPool()
,它們最適合您的用例。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.