第七章 取消与关闭
摘要: 本章主要介绍了如何结束一个任务的执行。虽然我们通常不会去打断,但要使线程安全、快速、可靠地停下来还是相对复杂的。本章主要的取消分两个方向,第一个就是任务的取消,第二个就是生成任务的线程的取消。
7.1 任务取消
最开始的例子是使用一个标志位每次调用任务之前先检查,这是典型地先检查后执行的代码。那么当任务中含有阻塞的任务时,这个任务就有可能一直阻塞在这里永远不会被返回。
第二个例子就是使用Thread的interrupt()方法来中断任务线程,调用interrupt()并不会立刻中断而是向任务线程递交了中断申请。
第三个例子就是当我们使用Executor框架时通过返回一个Future来表达带返回结果的任务,可以通过Future.get(timeout, unit)来设置限时任务等等。
当处理不可中断的阻塞时,本章给出的方法是实现一个CancelableTask去实现cancel()方法,例如获取socket时发生的阻塞时无法被外部中断打断的。那么在cancel()方法中我们就需要做对应的处理比如调用socket.close()方法来解决阻塞,这样的话任务就可以处理中断了。
7.2 停止基于线程的服务
说完了任务接下来就该说如何取消或关闭调用任务的线程,通常来讲调用任务的线程的生命周期比任务的生命周期要长。
以ExecutorService为例,他提供了两种中断的方法shotDown()和shotDownNow()方法。这两个方法是在响应性和安全性做的权衡。shotDown()会不在接受新的任务,但是会等待所有的任务全部结束后才会返回,而shotDownNow()方法则会立刻中断当前任务并且取消所有未开始的任务,那么这个方法有一个局限就是我们无法直接的辨认出哪些任务是在运行是被中断的哪些任务是取消的。一个设计相对良好的系统会解决这个问题。
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);
}
}
});
}
}
在主线程是工作队列的情况下可以通过 "毒丸" 来结束进程,例如当我们向工作队列中传入一个对象为空时,我们希望当调用者发现时就终结线程,这样就关闭了主线程。
7.3 非正常线程的终止
除了上述我们需要取消或关闭的情况,我们还需要cover一些不期望的UncaughtException的发生,因为当这些异常发生时对主程序以线程池为例,首先会返回错误的结果其次有可能会创建一个新的线程补充进池中甚至不会返回。那么我们就需要有一个GlobalUncaughtExceptionHandler来处理这些异常。
7.4 JVM关闭
不仅任务和主线程可以取消或者关闭,JVM的线程也需要正确的关闭。只有当所有的正常线程结束后,来结束JVM的线程。通常情况下会先调用关闭hook来进行一些关闭前的操作。
总结: 本章主要介绍了多种取消和关闭的策略和方法。