理解Java线程的中断

来源:互联网 发布:刻光盘软件那个好 编辑:程序博客网 时间:2024/06/05 10:07
一个线程在未正常结束之前, 被强制终止是很危险的事情. 因为它可能带来完全预料不到的严重后果. 所以你看到Thread.suspend, Thread.stop等方法都被Deprecated了.
那么不能直接把一个线程搞挂掉, 但有时候又有必要让一个线程死掉, 或者让它结束某种等待的状态 该怎么办呢? 优雅的方法就是, 给那个线程一个中断信号, 让它自己决定该怎么办. 比如说, 在某个子线程中为了等待一些特定条件的到来, 你调用了Thread.sleep(10000), 预期线程睡10秒之后自己醒来, 但是如果这个特定条件提前到来的话, 你怎么通知一个在睡觉的线程呢? 又比如说, 主线程通过调用子线程的join方法阻塞自己以等待子线程结束, 但是子线程运行过程中发现自己没办法在短时间内结束, 于是它需要想办法告诉主线程别等我了. 这些情况下, 就需要中断. 
中断是通过调用Thread.interrupt()方法来做的. 这个方法通过修改了被调用线程的中断状态来告知那个线程, 说它被中断了. 对于非阻塞中的线程, 只是改变了中断状态, 即Thread.isInterrupted()将返回true; 对于可取消的阻塞状态中的线程, 比如等待在这些函数上的线程, Thread.sleep(), Object.wait(), Thread.join(), 这个线程收到中断信号后, 会抛出InterruptedException, 同时会把中断状态置回为false.
下面的程序会演示对非阻塞中的线程中断:
[java] view plain copy
  1. public class Thread3 extends Thread{  
  2.     public void run(){  
  3.         while(true){  
  4.             if(Thread.interrupted()){  
  5.                 System.out.println("Someone interrupted me.");  
  6.             }  
  7.             else{  
  8.                 System.out.println("Going...");  
  9.             }  
  10.             long now = System.currentTimeMillis();  
  11.             while(System.currentTimeMillis()-now<1000){  
  12.                 // 为了避免Thread.sleep()而需要捕获InterruptedException而带来的理解上的困惑,  
  13.                 // 此处用这种方法空转1秒  
  14.             }  
  15.         }  
  16.     }  
  17.       
  18.     public static void main(String[] args) throws InterruptedException {  
  19.         Thread3 t = new Thread3();  
  20.         t.start();  
  21.         Thread.sleep(3000);  
  22.         t.interrupt();  
  23.     }  
  24. }  
下面的程序演示的是子线程通知父线程别等它了:
[java] view plain copy
  1. public class Thread4 extends Thread {  
  2.     private Thread parent;  
  3.     public Thread4(Thread parent){  
  4.         this.parent = parent;  
  5.     }  
  6.       
  7.     public void run() {  
  8.         while (true) {  
  9.             System.out.println("sub thread is running...");  
  10.             long now = System.currentTimeMillis();  
  11.             while (System.currentTimeMillis() - now < 2000) {  
  12.                 // 为了避免Thread.sleep()而需要捕获InterruptedException而带来的理解上的困惑,  
  13.                 // 此处用这种方法空转2秒  
  14.             }  
  15.             parent.interrupt();  
  16.         }  
  17.     }  
  18.       
  19.     public static void main(String[] args){  
  20.         Thread4 t = new Thread4(Thread.currentThread());  
  21.         t.start();  
  22.         try {  
  23.             t.join();  
  24.         } catch (InterruptedException e) {  
  25.             System.out.println("Parent thread will die...");  
  26.         }  
  27.     }  
  28. }  
中断状态可以通过 Thread.isInterrupted()来读取,并且可以通过一个名为 Thread.interrupted()的静态方法读取和清除状态(即调用该方法结束之后, 中断状态会变成false)。
由于处于阻塞状态的线程 被中断后抛出exception并置回中断状态, 有时候是不利的, 因为这个中断状态可能会作为别的线程的判断条件, 所以稳妥的办法是在处理exception的地方把状态复位:
[java] view plain copy
  1. boolean interrupted = false;  
  2. try {  
  3.     while (true) {  
  4.         try {  
  5.             return blockingQueue.take();  
  6.         } catch (InterruptedException e) {  
  7.             interrupted = true;  
  8.         }  
  9.     }  
  10. finally {  
  11.     if (interrupted){  
  12.         Thread.currentThread().interrupt();  
  13.     }  
  14. }  

当代码调用中须要抛出一个InterruptedException, 你可以选择把中断状态复位, 也可以选择向外抛出InterruptedException, 由外层的调用者来决定.

不是所有的阻塞方法收到中断后都可以取消阻塞状态, 输入和输出流类会阻塞等待 I/O 完成,但是它们不抛出 InterruptedException,而且在被中断的情况下也不会退出阻塞状态. 

尝试获取一个内部锁的操作(进入一个 synchronized 块)是不能被中断的,但是 ReentrantLock 支持可中断的获取模式即 tryLock(long time, TimeUnit unit)。