任务和线程的启动很容易。在大多数时候,我们都会让它们运行直到结束,或者让它们自行停止。然而,有时候我们希望提前结束任务或线程,或许是因为用户取消了操作,或者应用程序需要被快速关闭。 要使任务和线程能安仝、快速、可靠地停止下来,并不是一件容易的事。Java的Thread类为我们提供了stop(),suspend()等停止挂起线程的方法,但是由于安全问题目前都已被弃用。Java并没有提供一种安全的抢占式方法来停止线程,但它提供了中断(Interruption),这是一种协作机制,采用协作式的方式使一个线程终止另一个线程的当前工作。 这种协作式的方法是必要的,我们很少希望某个任务、线程或服务立即停止,因为这种立即停止会使共享的数据结构处于不一致的状态。相反,在编写任务和服务时可以使用一种协作的方式:当需要停止时,它们首先会清除当前正在执行的工作,然后再结束。这提供了更好的灵活性,因为任务本身的代码比发出取消请求的代码更清楚如何执行清除工作。 生命周期结束(End-of-Lifecycle)的问题会使任务、服务以及程序的设计和实现等过程变得复杂,而这个在程序设计中非常重要的要素却经常被忽略。一个在行为良好的软件与勉强运行的软件之间的最主要区别就是,行为良好的软件能很完善地处理失败、关闭和取消等过程。
如何设计一种协作机制,让线程可以安全的中断呢?我们可以设置一个取消标志,在工作线程会被中断的地方去检查这个标志,当检查到这个中断标志被设置为已取消时,工作线程便开始做取消工作。
1 public class CancelableThread implements Runnable { 2 3 //线程取消标志,volatile修饰,保证内存可见性 4 private volatile boolean isCanceled = false; 5 6 @Override 7 public void run() { 8 while (!isCanceled) {//在工作线程中轮询检测这个取消标志 9 System.out.println("The current thread is doing something..."); 10 System.out.println(Thread.currentThread().getName() + " cancel flag is " + isCanceled); 11 } 12 //当取消标志被设置为true,执行以下代码,可以做一些取消工作 13 System.out.println(Thread.currentThread().getName() + "The current thread Has been cancelled"); 14 } 15 16 private void cancel() { 17 isCanceled = true; 18 } 19 }
1 public class Main { 2 public static void main(String[] args) throws Exception { 3 4 CancelableThread cancelableThread = new CancelableThread(); 5 new Thread(cancelableThread).start(); 6 try { 7 Thread.sleep(1); 8 } finally { 9 //设置标志位为true,中断线程 10 cancelableThread.cancel(); 11 } 12 } 13 }
打印结果:
Thread-0 cancel flag is false
The current thread is doing something...
Thread-0 cancel flag is false
The current thread is doing something...
Thread-0 cancel flag is false
The current thread is doing something...
Thread-0 cancel flag is false
The current thread is doing something...
Thread-0 cancel flag is true
Thread-0The current thread Has been cancelled
其实Thread类为我们提供了三个与线程中断相关的方法,来实现上述机制。这三个方法分别是:
public void interrupt() { //... 省略相关代码 interrupt0(); // Just to set the interrupt flag //... 省略相关代码 }
public static boolean interrupted() { return currentThread().isInterrupted(true); }
public boolean isInterrupted() { return isInterrupted(false); }
- interrupt()方法主要用来设置中断标志位;如果此线程在调用Object类的wait(),wait(long)或wait(long,int)方法或join(),join(long),join(long,int) ,sleep(long)或sleep(long,int)的方法阻塞时,那么它的中断状态将被清除,并且会收到一个InterruptedException异常。
- 静态的interrupted()方法用来测试当前线程是否被中断,调用此方法会清除线程的中断状态。如果线程已被中断,调用此方法返回false;
- isInterrupted()方法用来测试当前线程是否被中断,但是不会清除线程的中断状态。
查看源码发现,静态的interrupted()和isInterrupted()方法都是调用的 private native boolean isInterrupted(boolean ClearInterrupted); 根据传入的ClearInterrupted的值,来判断是否要清除中断标志位。
1 public class InterruptTest { 2 3 4 static class InnerThread extends Thread{ 5 6 7 @Override 8 public void run() { 9 while(!isInterrupted()){ 10 System.out.println(Thread.currentThread().getName()+" cancle flag is "+isInterrupted()); 11 try { 12 13 Thread.sleep(100); 14 15 }catch (InterruptedException e){ 16 e.printStackTrace(); 17 //抛出InterruptedException,中断标志位被清除,再次调用 interrupt(); 18 interrupt(); 19 } 20 } 21 System.out.println(Thread.currentThread().getName()+" cancle flag is "+isInterrupted()); 22 23 } 24 } 25 26 27 public static void main(String[] args) { 28 InnerThread innerThread = new InnerThread(); 29 innerThread.start(); 30 try { 31 32 Thread.sleep(1000); 33 34 }catch (InterruptedException e){ 35 e.printStackTrace(); 36 } 37 innerThread.interrupt(); 38 // InnerThread innerThread2 = new InnerThread(); 39 // innerThread2.start(); 40 // innerThread2.interrupt(); 41 } 42 }
打印结果:
Thread-0 cancle flag is false Thread-0 cancle flag is false Thread-0 cancle flag is false Thread-0 cancle flag is false Thread-0 cancle flag is false Thread-0 cancle flag is false Thread-0 cancle flag is false Thread-0 cancle flag is false Thread-0 cancle flag is false Thread-0 cancle flag is false java.lang.InterruptedException: sleep interrupted at java.lang.Thread.sleep(Native Method) at InterruptTest$InnerThread.run(InterruptTest.java:13) Thread-0 cancle flag is true
参考资料:《Java并发编程实战》