启动一个线程是很容易的,无论是继承Thread类或者实现Runnable方法。大多数时候我们都是希望它能正常运行直到结束,或者自行停止。然而有时候我们希望能够提前结束线程,这个可能是用户取消了某个操作,或者应用程序需要快速关闭。
Java语言本身没有提供任何机制来安全的终结某个线程。但是提供了中断(Interruption),这是一种协作机制,能使一个线程终止另一个线程。这种协作式的机制式式很有必要的,因为我们很少需要一个线程立即停止,这样会造成数据不一致的状态(这就是Thread.stop()为什么被弃用的原因)。当需要停止时,首先清除当前正在执行的工作,然后再结束。
取消一个操作的原因
- 用户请求取消。比如点击 “取消” 按钮
- 有时间限制的操作。比如在有限时间内搜索问题解空间,并得到最优解。当计时器超时的时候,取消正在搜索的任务。
- 应用程序事件。将上述搜索问题解空间的任务分解,当一个任务找到最佳解决方案后,则其他正在执行搜索任务的线程将被取消。
- 错误。比如一个爬虫在搜索网页,并保存到磁盘。当磁盘满时,或者其他错误,所有的搜索任务都会结束。
- 关闭。当一个程序或服务关闭时,对正在执行的任务平缓的关闭(即等待其继续执行完成),或立即关闭(取消任务)。
取消标志
设置一个表示“已请求取消”的标志,然后定期地查看该标志。如果设置了这个标志那么任务将提前结束。下面举个例子:
public class SimpleCancle implements Runnable{
private volatile boolean flag;
public void cancle(){
flag = true;}
@Override
public void run() {
int i = 0;
while (!flag) {
System.out.println(i++);
}
System.out.println("run is finished");
}
public static void main(String[] args) {
SimpleCancle sample = new SimpleCancle();
Thread test = new Thread(sample);
test.start();
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
sample.cancle();
}
}
}
这里的while循环如果没有设置标志位,那么会一直执行,消耗cpu时钟周期。通过这种设置标志位的行为进行取消可能会存在一定的延迟,就是说在调用cancle方法和run方法执行下一次检查之间可能存在延迟。在finally块中保证一定能取消。
中断
上述使用取消表示确实可以停止任务并退出,虽然可能存在一定的延迟。但是如果while循环中调用了一个阻塞的方法,比如BlockingQueue.put,那么这个任务可能永远不会检查标志,因此永远不会结束。
举个例子:
public class NeverStop implements Runnable{
private final BlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>