java中的线程中断机制

  Java中的许多方法,如 Thread.sleep(), Object.join() 和 Object.wait()等,都可以抛出InterruptedException,这是一个受检异常(checked exception),因此必须在代码中被捕获。通常,当一个方法抛出 InterruptedException,说明这是一个阻塞(blocking)方法,阻塞方法可能因为等不到所等的事件而无法终止,因此令阻塞方法可取消就非常有用。

  Java的线程中断机制是一种协作机制,能够使一个线程通知另一个线程,告诉目标线程在合适的情况下终止当前工作。

线程中断

  每个Java线程都有一个用于表示线程中断状态(interrupted status)的标志,其初始值为false。
当在一个线程中,调用了另一个目标线程的 interrupt( )方法时,会发生以下两种情况之一:

  1. 如果目标线程正在执行阻塞方法,例如 Thread.sleep( ), Thread.join( ) 或 Object.wait( ),那么它将取消阻塞并抛出InterruptedException,并且,目标线程的interrupted status标志不会被设置为true。

  2. 当线程在非阻塞状态下时,interrupt() 只是设置目标线程的中断状态,在被中断的目标线程中运行的代码以后可以轮询中断状态,看看它是否被请求停止正在做的事情。这个中断状态可以通过两种方式读取:

    static boolean interrupted() #清除当前线程的中断状态,并返回它之前的值
    
    boolean isInterrupted()     #返回目标线程的中断状态
    

  其中,静态的interrupted( )方法会清除当前线程的interrupted status标志,而非静态的isInterrupted( )方法,通常用于一个线程查询另一个线程的interrupt status标志,不会改变被查询线程的中断标志位。

  调用interrupt( )并不意味着立即停止目标线程正在执行的工作,而只是传递了请求中断的消息,通常,中断是实现取消的最合理方式 ——Java并发编程实践

  以下代码展示一个可取消的线程,通过在另一个线程中调用cancel( )方法就可以退出该线程。可以通过这种生吞中断来达到线程退出的模式来实现可取消的任务。

public class StoppedThread {
    public void run() {
       try {
          while(!Thread.currentThread().isInterrupted()) {
             // ...
          }
       } catch (InterruptedException e)
          /* Allow thread to exit */
       }

    }

    public void cancel() { 
        interrupt(); 
    }
}

响应中断

  阻塞方法通常会抛出一个InterruptedException来表示中断异常,通常有两种策略用来处理InterruptedException。

  • 传递异常:一种最容易的策略就是在声明方法时抛出 InterruptedException,将异常向上抛出,由该方法的调用者来处理该异常。

  • 恢复中断状态:当阻塞方法检测到中断时,它们响应中断时执行的操作包括:清除中断状态,抛出 InterruptedException 并提前结束阻塞。更多情况下,我们需要保留中断发生的证据,以便调用栈中更高层的代码能知道中断并采取进一步的操作。因此,需要通过调用interrupt( ) 以 “重新中断” 当前线程来达到恢复中断状态的目的。

    public class TaskRunner implements Runnable {
        private BlockingQueue<Task> queue;
        public TaskRunner(BlockingQueue<Task> queue) { 
            this.queue = queue; 
        }
    
        public void run() { 
            try {
                while (true) {
                    Task task = queue.take(10, TimeUnit.SECONDS);
                    task.execute();
                }
            } catch (InterruptedException e) { 
                // Restore the interrupted status
                Thread.currentThread().interrupt();
            }
        }
    }
    

  对于一些不支持取消但仍可以调用可中断阻塞方法的操作,它们必须在循环中调用这些方法,并在发现中断后重新尝试。在这种情况下,仍然需要保留中断状态,但却是在方法返回前恢复中断状态而不是在捕获InterruptedException时恢复状态。因为大多数可中断的阻塞方法都会在入口处检查中断状态并立即抛出InterruptedException,如果过早地发现并恢复中断,则会引起无线循环。

public class NoncancelableTask {
public Task getNextTask(BlockingQueue<Task> queue) {
    boolean interrupted = false;
    try {
        while (true) {
            try {
                return queue.take();
            } catch (InterruptedException e) {
                interrupted = true;
                // fall through and retry
            }
        }
    } finally {
        if (interrupted)
            Thread.currentThread().interrupt();
    }
}

不可中断的阻塞方法

  并非所有的阻塞方法都抛出 InterruptedException。输入和输出流类会阻塞等待 I/O 完成,但是它们不抛出 InterruptedException,而且在被中断的情况下也不会提前返回。 类似地,尝试获取一个内部锁的操作(进入一个 synchronized 块)是不能被中断的,但是 ReentrantLock 支持可中断的获取模式。

参考文献:

http://stackoverflow.com/questions/671049/how-do-you-kill-a-thread-in-java
http://www.ibm.com/developerworks/cn/java/j-jtp05236.html
http://stackoverflow.com/questions/3590000/what-does-java-lang-thread-interrupt-do
http://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html#interrupt--
http://docs.oracle.com/javase/tutorial/essential/concurrency/interrupt.html