JAVA线程停止的最佳实践(二)

这里主要介绍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能抛出的就抛出。

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

  2. 由于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的同时,根据场景进行相关的处理,比如关闭流等。

你可能感兴趣的:(Android,java)