这里主要介绍interrupt的正确使用方式。

关于线程停止的常见的错误,请看上一篇错误的停止方式:两种常见错误

正确的停止方式:如何使用interrupt

正确的处理方式只有一个,那就是通过interrupt()方法。下面分三种情况介绍如何正确使用Interrupt()

1. 没有阻塞函数的线程停止

这种情况比较简单,只需要在代码合适的位置检查线程是否中断即可。检测到中断后可以自己处理中断后的业务逻辑。

private static Runnable runnable = () -> {
         while (!Thread.currentThread().isInterrupted()) {
            System.out.println("从银行卡扣掉此人1W元");
            for (int j = 1; j < 11; j++) {
                System.out.println("给了" + 1000 * j);
            }
        }
    };
		//停止线程
		t.interrupt();
2. 有阻塞函数的线程停止

此处的阻塞函数是指会抛出InterruptedException的相关函数,比如常见的wait()sleep(),以及BlockingQueuetake()put()等方法,有这类函数的线程停止要依照两个原则。

  1. 能抛出的就抛出
    尽量在方法里抛出捕获InterruptedException而不是捕获后什么也不做。抛出异常的目的是为了尽量把异常的处理权交给上级调用者。
  2. 不抛出要恢复
    如果不想抛出,或者有的方法不能抛出InterruptedException,那么要恢复中断。

通过代码来看下上面两个问题,简单的改动下上面的代码,将sleep封装为一个自己捕获异常的函数移出来。

private static Runnable runnable = () -> {
  			//检验线程是否中断
        while (!Thread.currentThread().isInterrupted()) {
            System.out.println("从银行卡扣掉此人1W元");
            for (int j = 1; j < 11; j++) {
                System.out.println("给了" + 1000 * j);
                sleepWithoutException();
            }
        }
      	System.out.println("线程停止");
    };

    /**
     * 不抛出异常的sleep,自己将异常捕获
     */
    private static void sleepWithoutException() {
        try {
            //等待印钞机印钱
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(runnable);
        t.start();
        Thread.sleep(1000);
        t.interrupt();
    }

想象一下调用t.interrupt()后线程会停止吗。运行后控制台的打印数据如下。

从银行卡扣掉此人1W元
...
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	...
给了5000
给了6000
....

为什么调用了interrupt()后线程依旧没有停止呢?与上面对应,答案呢,也分两点。

  1. sleepWithoutException()没有抛出向上传递异常,导致上层调用函数不知道线程已经停止了。
    不知道线程停止了,没法捕获自然也没法处理了。所以按照第一条原则就要把这个InterruptedException抛出,而不是自己吞了。按照这样把代码改成如下。
private static Runnable runnable = () -> {
        try {
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("从银行卡扣掉此人1W元");
                for (int j = 1; j < 11; j++) {
                    System.out.println("给了" + 1000 * j);
                    sleepThrowException();
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
            //todo 没给够钱的重新给
            System.out.println("线程被中断了,没给够钱的重新给");
        }
        System.out.println("线程停止");
    };

    /**
     * 抛出异常的sleep
     */
    private static void sleepThrowException() throws InterruptedException {
        Thread.sleep(10);
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(runnable);
        t.start();
        Thread.sleep(1000);
        t.interrupt();
    }

运行后输出

...
给了2000
线程被中断了,没给够钱的重新给
线程停止
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at startthread.StopThread2.sleepThrowException(StopThread2.java:25)
	at startthread.StopThread2.lambda$static$0(StopThread2.java:11)
	at java.lang.Thread.run(Thread.java:748)

Process finished with exit code 0

可见这时调用t.interrupt();后,线程如预期那样停止了。

所以针对InterruptedException能抛出的就抛出。

那么不能抛出或者实在不想抛出怎么办呢?下面就是第二种情况了。

  1. 由于InterruptedException会重置中断标志位,所以不能抛出的要恢复中断
    细心人肯定已经发现代码中有这么一行
while (!Thread.currentThread().isInterrupted())

那么为什么中断后还是没能停止呢,原来发生InterruptedException时,会重置线程的isInterrupted标志位,所以上面while循环自然也不会跳出了。那么我们不想或者不能抛出异常时,就要恢复中断标志位。

上面的代码改成这样就OK啦。

private static Runnable runnable = () -> {
        while (!Thread.currentThread().isInterrupted()) {
            System.out.println("从银行卡扣掉此人1W元");
            for (int j = 1; j < 11; j++) {
                System.out.println("给了" + 1000 * j);
                sleepWithoutException();
            }
        }
        System.out.println("线程停止");
    };

    /**
     * 不抛出异常的sleep,要恢复中断
     */
    private static void sleepWithoutException() {
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
            //恢复中断
            Thread.currentThread().interrupt();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(runnable);
        t.start();
        Thread.sleep(1000);
        t.interrupt();
    }
3. 有阻塞,但无法响应中断的线程停止

并不是所有的阻塞函数都能够响应中断的,比如常见的IO操作,ReentrantLock的lock()等,这种的处理逻辑一般是在interrupt的同时,根据场景进行相关的处理,比如关闭流等。