我从实践中的并发性读取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
:
履带:
/**
* 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
方法的逻辑。
答案 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
),仍会有一些未处理的任务。