2.5 线程的中断

2016-05-31 23:01:24 4,659 0

中断(interrupt)表示一个线程应该停止当前所做的事而去另外一件事。通常中断是一个线程给另外一个线程发送中断信号,程序员自行决定如如何进行响应,也就是说收到中断信号后,接下来该做什么。通常情况下,线程收到中断信号后,采取的操作都是停止运行。

我们可以在一个线程对象A中调用另一个线程对象B的interrupt方法,此时线程B就会收到中断信号。

支持中断

1、在运行时代码中调用了可以抛出InterruptedException的方法

线程如何支持中断?这依赖于线程的运行时代码是如何编写的。如果在运行时代码中调用了可以抛出InterruptedException的方法,那么线程在接受到的中断信号就体现在这个异常上。

以下用代码进行演示:

public class InterruptDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(){
            @Override
            public void run() {
                //预期循环10次
                for (int i = 0; i <10 ; i++) {
                    try {
                        Thread.sleep(4000);
                        System.out.println("自定义线程:当前时间:"+new Date().toLocaleString());
                    } catch (InterruptedException e) {//这个异常由sleep方法抛出
                        e.printStackTrace();
                        System.out.println("自定义线程:收到中断信号,总共循环了"+i+"次...");
                        //接受到中断信号时,停止运行
                        return;
                    }
                }
            }
        };
        t.start();
        //主线程休眠9秒
        Thread.sleep(9000);
        System.out.println("主线程:等待9秒后发送中断信号...");
        t.interrupt();
    }
}

以上代码中,我们在一个自定义的线程的run方法中,预期循环10次,每一次循环打印出当前时间并休眠4秒,当收到中断信号时,停止运行。而主线程在9秒后,给线程t发送了中断信号,因此,线程t理论上只能打印出2次当前时间。

程序运行结果如下:

自定义线程:当前时间:2016-5-31 22:23:17

自定义线程:当前时间:2016-5-31 22:23:21

主线程:等待9秒后发送中断信号...

自定义线程:收到中断信号,总共循环了2次...

java.lang.InterruptedException: sleep interrupted

at java.lang.Thread.sleep(Native Method)

at concurrency.chapter2.InterruptDemo$1.run(InterruptDemo.java:16)

可以看到自定义线程的确是打印出两次当前时间后就停止了运行,根本原因在于,我们在收到中断信号后,在catch代码中使用了return,结束了方法。读者可以尝试将return去掉,这个时候,即使收到了中断信号,也会继续打印10次!

2、在运行时代码中没有调用可以抛出InterruptedException的方法

如果在运行时代码中没有调用可以抛出中断异常的方法,那么我们必须频繁的调用Thread类的静态方法interrupted()来判断是否收到一个中断信号。

public class InteruptDemo2 {
    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(){
            @Override
            public void run() {
                int i=0;
                while (true){
                    //每次打印前都判断是否被中断
                    if (!Thread.interrupted()) {
                        i++;
                        System.out.println("自定义线程,打印...."+i+"次");
                    }else{//如果被中断,停止运行
                        System.out.println("自定义线程:被中断...");
                        return;
                    }

                }

            }
        };
        t.start();
        //主线程休眠1毫秒,以便自定义线程执行
        Thread.sleep(1);
        System.out.println("主线程:休眠1毫秒后发送中断信号...");
        t.interrupt();
    }
}

以上代码中,主线程休眠1毫秒,以便自定义线程可以得到执行,之后发送中断信号,注意每次运行的结果可能不一样。运行结果如下:

自定义线程,打印....1次

自定义线程,打印....2次

自定义线程,打印....3次

自定义线程,打印....4次

自定义线程,打印....5次

自定义线程,打印....6次

主线程:休眠1毫秒后发送中断信号...

自定义线程,打印....7次

自定义线程:被中断...

可以看到,在收到中断信号后,自定义线程停止了运行。不过,为什么自定义线程t在收到中断信号后还执行了一次呢?这是因为中断信号发送的后,t线程正好运行到了打印的代码,因此只有到下一次循环的时候才会检测到中断信号,停止运行。如果你多运行几次的话,会发现每次的结果都是不一样的。

中断状态标记

中断机制的实现是通过一个标记中断状态(interrupt status)实现的。我们通过调用某个线程对象的interrupt方法来设置这个标记。当一个线程通过Thread的类的静态方法interrupted判断到自己被中断后,立即会将这个状态清空。在其他的线程中,我们可以通过调用某个线程对象的isInterrupted方法判断这个线程是否被中断,但是不会中断状态清空。

按照惯例,当一个方法接受到中断信号时,应该以抛出InterruptedException的方式退出执行。

注意,这种方式很重要,在后文我们将要提供的线程池中,其停止线程池时,就通过发送中断信号。而我们提交给线程池运行的Runnable或者Callable任务代码中,必须能响应中断,否则线程池可能会无法停止。