第七章 取消与关闭
Java没有提供任何机制来安全地终止线程,但是它提供了中断,这是一种协作机制,能够使一个线程终止另一个线程的当前工作。
1.通过取消标志
可以用一个volatile类型的域来保存取消状态,在每次执行任务前都进行检查。但是这种方法有可能会产生问题,比如如果任务调用了一个阻塞方法,那么取消标志可能永远不会被检查,因此永远不会结束。
2.通过中断
Thread中的中断方法
public class Thread{
public void interrupt(){...}
public boolean isInterrupted(){...}
public static boolean interrupted(){...}
}
void Interrupt():中断目标线程
boolean isInterrupted():返回目标线程的中断状态
static boolean interrupted():清除当前线程的中断状态 ,除非你想屏蔽掉这个中断,否则你要对它处理:抛出InterruptedException或者再次调用interrupt恢复中断
对中断的理解应该是:它并不会真正中断一个正在运行的线程; 它仅仅只是发送中断请求(这一点很重要)
例子:
/**
* 通过中断来取消
* @author cream
*
*/
public class PrimeProducer extends Thread {
private final BlockingQueue<BigInteger> queue;
public PrimeProducer(BlockingQueue<BigInteger> queue) {
this.queue = queue;
}
@Override
public void run() {
try{
BigInteger p = BigInteger.ONE;
while (!Thread.currentThread().isInterrupted()) {
queue.put(p.nextProbablePrime());
}
} catch (InterruptedException e) {
//允许线程退出
}
}
public void cancel() {
interrupt();
}
}
当调用可中断的阻塞函数时,有两种策略用来处理InterruptedException:
传递异常,从而使你的方法也成为可中断的阻塞方法。
恢复中断状态,从而使调用栈中的上层代码能够对其进行处理。
/**
* 不可取消的任务在退出前恢复中断
*/
public Task getNextTask(BlockingQueue<Task> queue){
boolean interrupted = false;
try {
while(true){
try {
return queue.take();
} catch (InterruptedException e) {
interrupted = true;//重新尝试
}
}
} finally {
if(interrupted)
Thread.currentThread().interrupt();
}
}
3.通过Future
ExecutorService.submit将返回一个Future来管理任务,Future拥有一个cancle方法
boolean cancel(boolean mayInterruptIfRunning);
如果mayInterruptIfRunning为true,并且任务当前正在某一个线程运行,那么这个线程能够被中断;
如果为false,那么意味着“如果任务还没有启动,则不要运行它”
当Future.get抛出InterruptedException或者TimeoutException时,如果你知道不再需要结果,那么就可以调用Future.cancle来取消任务。
4.关闭服务
ExecutorService提供两种关闭方法,使用shutdown正常关闭,以及使用shutdownNow强行关闭,使用正常关闭时,等到所有队列中的任务执行完毕后才关闭,使用强行关闭时,首先关闭当前正在执行的任务,然后返回所有尚未启动的任务清单。但是,shutdownNow无法通过常规方法来找出那些任务已经开始但尚未结束。
另一种关闭生产者消费者服务的方式是使用“毒丸”对象:一个放在队列上的对象,当得到这个对象时立即停止。消费者会处理毒丸之前的所有工作,而生产者在提交了毒丸对象后将不再提交任何工作。
5.jvm关闭
关闭钩子:在正常关闭中,jvm首先调用所有已注册的关闭钩子。
Runtime.getRuntime().addShutdownHook(shutdownHook);
这个方法的意思就是在jvm中增加一个关闭的钩子,当jvm关闭的时候,会执行系统中已经设置的所有通过方法addShutdownHook添加的钩子,当系统执行完这些钩子后,jvm才会关闭。所以这些钩子可以在jvm关闭的时候进行内存清理、对象销毁等操作。用途:
1应用程序正常退出,在退出时执行特定的业务逻辑,或者关闭资源等操作。
2虚拟机非正常退出,比如用户按下ctrl+c、OutofMemory宕机、操作系统关闭等。在退出时执行必要的挽救措施
建议:同一个JVM最好只使用一个关闭钩子,而不是每个服务都使用一个不同的关闭钩子,使用多个关闭钩子可能会出现当前这个钩子所要依赖的服务可能已经被另外一个关闭钩子关闭了。为了避免这种情况,建议关闭操作在单个线程中串行执行,从而避免了再关闭操作之间出现竞态条件或者死锁等问题。
守护线程:
当需要创建线程来执行辅助工作,但又不希望这个线程阻碍jvm的关闭时用到。
当一个线程退出时,jvm会检查其他正在运行的线程,如果这些线程都是守护线程,那么jvm会正常退出操作,当jvm停止时,所有仍然存在的守护线程都会被抛弃——既不会执行finally代码块,也不会执行回卷栈,而jvm只是直接退出。