WebCrawler停止方法逻辑[实践中的并发7.2.5]

时间:2017-02-22 07:55:51

标签: java multithreading concurrency threadpool cancellation

我从实践中的并发性读取7.2.5章节(shutdownNow的限制)

shutdownNow的问题,它只返回未启动的任务。

首先,我们创建ExecutorService,在关机后跟踪已取消的任务。

TrackingExecutor:

/**
 * TrackingExecutor
 * <p/>
 * ExecutorService that keeps track of cancelled tasks after shutdown
 *
 * @author Brian Goetz and Tim Peierls
 */
public class TrackingExecutor extends AbstractExecutorService {
    private final ExecutorService exec;
    private final Set<Runnable> tasksCancelledAtShutdown =
            Collections.synchronizedSet(new HashSet<Runnable>());

    public TrackingExecutor(ExecutorService exec) {
        this.exec = exec;
    }

    public void shutdown() {
        exec.shutdown();
    }

    public List<Runnable> shutdownNow() {
        return exec.shutdownNow();
    }

    public boolean isShutdown() {
        return exec.isShutdown();
    }

    public boolean isTerminated() {
        return exec.isTerminated();
    }

    public boolean awaitTermination(long timeout, TimeUnit unit)
            throws InterruptedException {
        return exec.awaitTermination(timeout, unit);
    }

    public List<Runnable> getCancelledTasks() {
        if (!exec.isTerminated())
            throw new IllegalStateException(/*...*/);
        return new ArrayList<Runnable>(tasksCancelledAtShutdown);
    }

    public void execute(final Runnable runnable) {
        exec.execute(new Runnable() {
            public void run() {
                try {
                    runnable.run();
                } finally {
                    if (isShutdown()
                            && Thread.currentThread().isInterrupted())
                        tasksCancelledAtShutdown.add(runnable);
                }
            }
        });
    }
}

然后我们创建使用TrackingExecutor

的Crawler

履带:

/**
 * WebCrawler
 * <p/>
 * Using TrackingExecutorService to save unfinished tasks for later execution
 *
 * @author Brian Goetz and Tim Peierls
 */
public abstract class WebCrawler {
    private volatile TrackingExecutor exec;
    @GuardedBy("this") private final Set<URL> urlsToCrawl = new HashSet<URL>();

    private final ConcurrentMap<URL, Boolean> seen = new ConcurrentHashMap<URL, Boolean>();
    private static final long TIMEOUT = 500;
    private static final TimeUnit UNIT = MILLISECONDS;

    public WebCrawler(URL startUrl) {
        urlsToCrawl.add(startUrl);
    }

    public synchronized void start() {
        exec = new TrackingExecutor(Executors.newCachedThreadPool());
        for (URL url : urlsToCrawl) submitCrawlTask(url);
        urlsToCrawl.clear();
    }

    public synchronized void stop() throws InterruptedException {
        try {
            saveUncrawled(exec.shutdownNow());
            if (exec.awaitTermination(TIMEOUT, UNIT))
                saveUncrawled(exec.getCancelledTasks());
        } finally {
            exec = null;
        }
    }

    protected abstract List<URL> processPage(URL url);

    private void saveUncrawled(List<Runnable> uncrawled) {
        for (Runnable task : uncrawled)
            urlsToCrawl.add(((CrawlTask) task).getPage());
    }

    private void submitCrawlTask(URL u) {
        exec.execute(new CrawlTask(u));
    }

    private class CrawlTask implements Runnable {
        private final URL url;

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

        private int count = 1;

        boolean alreadyCrawled() {
            return seen.putIfAbsent(url, true) != null;
        }

        void markUncrawled() {
            seen.remove(url);
            System.out.printf("marking %s uncrawled%n", url);
        }

        public void run() {
            for (URL link : processPage(url)) {
                if (Thread.currentThread().isInterrupted())
                    return;
                submitCrawlTask(link);
            }
        }

        public URL getPage() {
            return url;
        }
    }
}

让研究stop方法:

 public synchronized void stop() throws InterruptedException {
     try {
         saveUncrawled(exec.shutdownNow()); //1
         if (exec.awaitTermination(TIMEOUT, UNIT)) //2
             saveUncrawled(exec.getCancelledTasks()); //3
         } finally {
             exec = null;
         }
     }
 }
saveUncrawled(exec.shutdownNow()); //1

在行1中,我们执行shutdownNow并保存已返回(未启动)的任务 如果我理解正确shutdownNow返回未启动的任务并中断已启动的任务

exec.awaitTermination(TIMEOUT, UNIT) //2

此外,我们希望将已取消的任务添加到此集合中。 在第2行,我们给出时间并等待超时终止。

问题№1

为什么我们为此操作提供timeOut?

据我了解 - shutdownNow无论如何都会中断进度任务。我没有理由等待。

exec.getCancelledTasks() 
如果任务成功完成,

awaitTermination方法返回true,因此我不清楚为什么我们尝试在这种情况下添加已取消的任务。

请澄清stop方法的逻辑。

1 个答案:

答案 0 :(得分:1)

关于boolean awaitTermination(long timeout, TimeUnit unit)的超时:

中断线程不一定立即停止(或根本不停止)。引用Java Tutorial on Interrupts

  

中断是一个线程的指示,它应该停止它正在做的事情并做其他事情。由程序员决定线程如何响应中断,但线程终止是很常见的。这是本课中强调的用法。

它也直接在ExecutorService#shutdownNow()的javadoc中拼写出来:

  

除了尽力尝试停止处理主动执行任务之外,没有任何保证。例如,典型的实现将通过Thread.interrupt()取消,因此任何无法响应中断的任务都可能永远不会终止。

Thread#interrupt()的javadoc中提到了线程在中断后仍然存活的其他原因。例如:

  

除非当前线程正在中断(总是允许),否则会调用此线程的checkAccess方法,这可能会导致SecurityException被抛出。

如果没有仔细研究ExecutorService的javadoc,stop()方法的逻辑就不明显了(参见&#34;用法示例&#34;第二个例子)。 shutdownNow()的问题在于它试图取消所有线程,但(a)这可能需要一些时间,(b)不能保证它成功(见上文)。 awaitTermination(long, TimeUnit)允许跟踪此进度。我将逐行完成stop()方法:

saveUncrawled(exec.shutdownNow());

启动ExecutorService的关闭并收集正在等待执行的任务。已完成的任务将被忽略,当前正在执行的任务也将被忽略。

if (exec.awaitTermination(TIMEOUT, UNIT))

shutdownNow()只是表示他们应该通过中断停止当前正在运行的任务。它不会杀死他们。此外,中断后停止工作需要时间。因此,您必须等待执行完成。超时是为了防止您永远阻止,以防某些任务永远不会完成(无论出于何种原因)。请记住,线程可以忽略中断,或者可能需要比剩余超时更长的时间才能停止工作。因此,在awaitTermination(TIMEOUT, UNIT)之后可能仍会有一些任务。 TrackingExecutor仅收集可以取消的任务。但不是超时到期后可能仍在执行的那些。

saveUncrawled(exec.getCancelledTasks());

如果可以取消所有任务,awaitTermination()将返回true。在这种情况下,收集所有已取消的任务。如果不能取消所有任务(即awaitTermination()返回false),仍会有一些未处理的任务。