Android “优雅”地中断线程

作者: 小鱼人爱编程 | 来源:发表于2019-11-27 23:46 被阅读0次

    在Android开发中,不可避免的会用到线程来执行耗时任务,那如果我们想在中途停止/中断任务的执行,该怎么办呢?先来看看一个简单的线程。

        private Thread threadOne = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    while(true) {
                        Log.d(TAG, "thread isAlive:" + threadOne.isAlive());
                        Thread.sleep(1000);
                    }
                } catch (Exception e) {
                    Log.d(TAG, e.getClass().toString());
                    Log.d(TAG, "thread isAlive in catch:" + threadOne.isAlive());
                }
            }
        });
    

    正常运行打印结果:


    image.png

    当使用interrupt()方法中断该线程时,打印如下:


    image.png
    可以看出,调用interrupt()后,会捕获名为“InterruptedException”的异常,但是接下来的发现线程还存活,这是怎么回事呢?既然线程能够被中断,那么是否提供查询中断状态的方法呢?通过查看api我们发现,thread.isInterrupted()可以查看线程的中断状态,因此我们再加一个打印:
                    Log.d(TAG, e.getClass().toString());
                    Log.d(TAG, "thread isInterrupted::" + threadOne.isInterrupted());
                    Log.d(TAG, "thread isAlive in catch:" + threadOne.isAlive());
    

    然而中断状态位依然是“未被中断”。这与我们想象的不太一样,因此回想一下是哪个方法抛出了异常,发现是sleep方法。

    // BEGIN Android-changed: Implement sleep() methods using a shared native implementation.
        public static void sleep(long millis) throws InterruptedException {
            sleep(millis, 0);
        }
    
    

    我们先把sleep()方法注释掉,再运行

        private Thread threadOne = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    while (!threadOne.isInterrupted()) {
                        Log.d(TAG, "thread isAlive:" + threadOne.isAlive());
                    }
                    Log.d(TAG, "break while thread isAlive:" + threadOne.isAlive());
                    Log.d(TAG, "break while thread isInterrupted::" + threadOne.isInterrupted());
                } catch (Exception e) {
                    Log.d(TAG, e.getClass().toString());
                    Log.d(TAG, "thread isInterrupted::" + threadOne.isInterrupted());
                    Log.d(TAG, "thread isAlive in catch:" + threadOne.isAlive());
                }
            }
        });
    
    image.png

    这次的结果比较符合我们的“直观想象”, 线程还是存活,但中断状态位标记位为true。

    从上面两个两个例子可知:
    1.中断正在阻塞(sleep)的线程,会抛出InterruptedException异常,中断标记位为false。
    2.中断未被阻塞的线程,中断标记位会被置为true。

    那么针对正在阻塞的线程,我们只需要捕获到InterruptedException异常就退出线程执行,对于未被阻塞的线程,判断中断标记是否为true,若是则退出线程执行。当然我们线程里如果调用了其它方法,不确定其它方法阻塞与否,因此可以将这两种判断结合起来,如下:

        private Thread threadOne = new Thread(new Runnable() {
            @Override
            public void run() {
    
                try {
                    while (!threadOne.isInterrupted()) {
                        doSomething();
                    }
    
                } catch (Exception e) {
                    if (e instanceof InterruptedException) {
                        isInterrupted = true;
                    }
                }
            }
        });
    

    也许你会疑惑说你上面的线程都是在while里跑,如果线程只走一次,怎么中断呢?只能尽可能在每个关键之处停止其执行。

        private Thread threadOne = new Thread(new Runnable() {
            @Override
            public void run() {
    
                try {
                    if (!threadOne.isInterrupted())
                        doSomething1();
                    else
                        return;
    
                    if (!threadOne.isInterrupted())
                        doSomething2();
                    else
                        return;
    
                    if (!threadOne.isInterrupted())
                        doSomething3();
                    else
                        return;
    
                } catch (Exception e) {
                    
                }
            }
        });
    

    除了sleep方法之外,还有其它方法会抛出异常不?实际上,在Thread源码里对此都有解释,我们来看看源码怎么说的。

        /**
         * Interrupts this thread.
         *
         * <p> Unless the current thread is interrupting itself, which is
         * always permitted, the {@link #checkAccess() checkAccess} method
         * of this thread is invoked, which may cause a {@link
         * SecurityException} to be thrown.
         *
         * <p> If this thread is blocked in an invocation of the {@link
         * Object#wait() wait()}, {@link Object#wait(long) wait(long)}, or {@link
         * Object#wait(long, int) wait(long, int)} methods of the {@link Object}
         * class, or of the {@link #join()}, {@link #join(long)}, {@link
         * #join(long, int)}, {@link #sleep(long)}, or {@link #sleep(long, int)},
         * methods of this class, then its interrupt status will be cleared and it
         * will receive an {@link InterruptedException}.
         *
         * <p> If this thread is blocked in an I/O operation upon an {@link
         * java.nio.channels.InterruptibleChannel InterruptibleChannel}
         * then the channel will be closed, the thread's interrupt
         * status will be set, and the thread will receive a {@link
         * java.nio.channels.ClosedByInterruptException}.
         *
         * <p> If this thread is blocked in a {@link java.nio.channels.Selector}
         * then the thread's interrupt status will be set and it will return
         * immediately from the selection operation, possibly with a non-zero
         * value, just as if the selector's {@link
         * java.nio.channels.Selector#wakeup wakeup} method were invoked.
         *
         * <p> If none of the previous conditions hold then this thread's interrupt
         * status will be set. </p>
    

    总结来说:

    • 在线程里使用sleep、wait、join等方法,当线程被中断时,中断状态位会被重置为false,并且抛出InterruptedException异常(这也是为什么我们第一个例子里thread.isInterrupted()为false的原因)
    • 在线程里使用nio InterruptibleChannel接口时,当线程被中断时,中断状态位会被重置为true,并且抛出ClosedByInterruptException异常
    • 在线程里使用nio Selector时,当线程被中断时,中断状态位会被重置为true
    • 如不属于上述条件,则中断状态位会被重置为true(对应我们上面说的没有阻塞的情况)

    thread.isInterrupted() 和 Thread.interrupted区别
    thread.isInterrupted()是对象方法,表示thread的中断状态。Thread.interrupted()是静态方法,表示当前线程的中断状态,举个例子:

            Log.d(TAG, " curThread is:" + Thread.currentThread().getName());
            Log.d(TAG, " Thread.currentThread().isInterrupted() before :" + Thread.currentThread().isInterrupted());
            Log.d(TAG, " Thread.interrupted() before :" + Thread.interrupted());
            Log.d(TAG, " threadOne.isInterrupted() before :" + threadOne.isInterrupted());
            Thread.currentThread().interrupt();
            Log.d(TAG, " Thread.currentThread().isInterrupted() after:" + Thread.currentThread().isInterrupted());
            Log.d(TAG, " Thread.interrupted() after :" + Thread.interrupted());
            Log.d(TAG, " Thread.currentThread().isInterrupted() after2:" + Thread.currentThread().isInterrupted());
            Log.d(TAG, " threadOne.isInterrupted() after :" + threadOne.isInterrupted());
    
    image.png

    从上面可以看出来,Thread.interrupted()调用后会重置中断状态为false,而thread.isInterrupted()却不会。

    总结

    1、线程正在执行sleep、join、wait等方法,此时线程处在WAITING/TIMED_WAITING状态,当执行thread.interrupt(),那么会抛出InterruptedException异常,线程中断标记位为false,线程停止运行;
    2、线程处在RUNNABLE状态,当执行thread.interrupt(),不会抛出异常,线程中断标记位为true,线程未停止运行;
    3、如果线程处在BLOCKED(Synchronized争抢锁)状态,当执行thread.interrupt(),不会抛出异常,线程中断标记位为true,线程未停止运行(这点也说明了Synchronized不可打断)

    更多关于线程状态的问题请移步:Java 线程状态

    相关文章

      网友评论

        本文标题:Android “优雅”地中断线程

        本文链接:https://www.haomeiwen.com/subject/vxmiictx.html