Two-phase Termination 模式通过将停止线程这个动作分解为准备阶段和执行阶段这两个阶段,提供了一种通用的用于优雅地停止线程的方法。所谓优雅是指可以等要停止的线程在其处理完待处理的任务后停止,而并不是强行停止。
准备阶段
该阶段的主要动作是“通知” 目标线程(欲停止的线程)准备进行停止。这一步会设置一个标志变量用于指示目标线程可以准备停止了。
能够对Thread.interrupt 做出响应的一些方法
方法 | 响应interrupt 调用抛出的异常 |
---|---|
Object.wait()、Object.wait(long timeout)、Object.wait(long timeout, int nanos) | InterruptedException |
Thread.sleep(long millis)/Thread.sleep(long millis,int nanos) | InerruptedException |
Thread.join()、Thread.join(long mills、Thread.Join(long millis, int nanos) | InerruptedException |
java.util.concurent.BlockingQueue.take() | InterruptedException |
java.util.concurrent.locks.Lock.lockInterruptibly() | InterruptedException |
java.nio.channels.InterruptibleChannel | java.nio.channels.ClosedByInterruptException |
执行阶段
该阶段的主要动作是检查准备阶段所设置的线程停止标志和信号,在此基础上决定线程停止的时机,并进行适当的“清理”操作。
toShutdown 这个变量为了保证内存的可见性而又能避免使用显式锁的开销,而采用了volatile 修饰。若采用boolean 作为线程停止标志的代码,只是这些变量没有volatile 修饰,对其访问也没有加锁,这就可能无法停止目标线程。
另外,某些场景下多个可停止线程实例可能需要共用一个线程停止标志。例如,多个可停止线程实例“消耗”同一个队列中的数据。当该队列为空且不再有新的数据入队列的时候,“消耗”该队列数据的可停止线程都应该被停掉。
AbstractTerminatableTread 类的构造器支持传入一个TerminationToken 实例就是为了支持这种场景。
java.util.concurrent.ThreadPoolExecutor 就使用了Two-phase termination 模式来停止其内部维护的工作者线程。ThreadPoolExecutor 实例的停止过程也是分为准备阶段(设置其运行状态为SHUTDOWN) 和执行阶段(工作者队列取空工作队列中的任务,然后终止线程)。