停止线程是一个目标简单而实现却不那么简单的任务。首先,Java没有提供直接的API用于停止线程此外,停止线程还有一些额外的细节需要考虑,如待停止的线程处于阻塞(如等待锁)或者等待状态(等待其他线程)、尚有未处理完的任务等。
Two-phase Termination 模式通过将停止线程这个动作分解为准备阶段和执行阶段这两个阶段,提供了一种通用的用于优雅地停止线程的方法。参考《JAVA多线程基础篇 3、如何优雅结束一个线程》
1. Two-phase Termination 机制
准备阶段。该阶段的主要动作是“通知”目标线程(欲停止的线程)准备进行停止。这一步会设置一个标志变量用于指示目标线程可以准备停止了。但是,由于目标线程可能正处于阻塞状态(等待锁的获得)、等待状态(如调用Object.wait)或者I/O(如InputStream.read)等待等状态,即便设置了这个标志,目标线程也无法立即“看到”这个标志而做出相应动作。
因此,这一阶段还需要通过调用目标线程的interrupt方法,以期望目标线程能够通过捕获相关的异常侦测到该方法调用,从而中断其阻塞状态、等待状态。
- 对于能够对interrupt方法调用做出响应的方法,目标线程代码可以通过捕获这些方法抛出的InterruptedException来侦测线程停止信号。
- 但也有一些方法(如InputStream.read)并不对interrupt调用做出响应,此时需要我们手工处理。
2.示例代码
核心代码如下,线程在执行过程中需要不断检测shutdownRequested标志。当shutdownRequested标志位True,任务停止。
public final void run() {
try {
while (!shutdownRequested) {
doWork();
}
} catch (InterruptedException e) {
} finally {
doShutdown();
}
}
总结
java.util.concurrent.ThreadPoolExecutor就使用了Two-phase Termination模式来停止其内部维护的工作者线程。当客户端代码调用ThreadPoolExecutor实例的shutdown方法请求其关闭时,ThreadPoolExecutor会先将其运行状态设置为SHUTDOWN。工作者线程的run方法会判断其所属的ThreadPoolExecutor实例的运行状态。若ThreadPoolExecutor实例的运行状态为SHUTDOWN,则工作者线程会一直取工作队列中的任务进行执行,直到工作队列为空时该工作者线程就停止了。
多线程系列在github上有一个开源项目,主要是本系列博客的实验代码。
https://github.com/forestnlp/concurrentlab
如果您对软件开发、机器学习、深度学习有兴趣请关注本博客,将持续推出Java、软件架构、深度学习相关专栏。
您的支持是对我最大的鼓励。