美文网首页软技能集 面试题集
学会优雅地停止线程,告别暴力停止线程

学会优雅地停止线程,告别暴力停止线程

作者: 怡红快绿 | 来源:发表于2019-10-17 21:59 被阅读0次

    在Android应用开发的很多场景下,为了不影响主线程及时响应用户的交互行为,我们通常需要将一些耗时任务放在子线程中执行,例如请求网络数据、读取数据库等等。假如在任务执行过程中发生了意外情况,需要终止任务,这个时候我们要做的就是让子线程停止运行。尽管这看起来是一件非常简单的事,但是我们还是需要对线程进行妥善处理,以免产生意想不到的结果。

    在介绍如何停止线程之前,先介绍一下如何判断线程是否处于中断状态。

    一、判断线程中断状态

    系统为我们提供了两种方法判断线程是否停止:

    • interrupted() 判断当前线程是否已经中断。
      当前线程指的是运行interrupted()方法的线程。
    • isInterrupted() 判断线程是否已经中断

    既然有两个方法,那么这两个方法肯定是有区别的。多说无益,直接看例子吧。

    1、interrupted()方法:

    public class InterruptedThread {
    
        public static void main(String... args) {
            MyThread myThread = new MyThread();
            myThread.start();
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            myThread.interrupt();
           //Thread.currentThread().interrupt();     //注释1
            System.out.println("thread state1=" + Thread.interrupted());
            System.out.println("thread state2=" + Thread.interrupted());
    
        }
    
        public static class MyThread extends Thread {
            @Override
            public void run() {
                super.run();
                for (int i = 0; i < 50000; i++) {
                    System.out.println("run: " + i);
                }
            }
        }
    }
    
    ……
    run: 1493
    run: 1494
    run: 1495
    run: 1496
    run: 1497
    thread state1=false
    thread state2=false
    run: 1498
    run: 1499
    run: 1500
    run: 1501
    run: 1502
    run: 1503
    ……
    

    从运行结果来看,虽然我们调用了myThread.interrupt()方法 ,但是两次返回结果都是false,这也就证明了interrupted()方法确实是判断当前线程是否中断。这里的当前线程也就是指main线程,它从未被中断过,所以结果为false。

    为了让main线程产生中断效果,可以试着用上面注释1处的代码替换myThread.interrupt()代码。

    Thread.currentThread().interrupt();     //注释1
    System.out.println("thread state1=" + Thread.interrupted());
    System.out.println("thread state2=" + Thread.interrupted());
    

    观察到运行结果如下:

    ……
    run: 4118
    run: 4119
    run: 4120
    run: 4121
    run: 4122
    thread state1=true
    run: 4123
    thread state2=false
    run: 4124
    run: 4125
    run: 4126
    run: 4127
    ……
    

    我们可以看到,interrupted()方法第一次返回的是true,这和我们预想的是一样的,当前线程main确实被成功中断了。但是为什么第二次又变成了false呢?那是因为interrupted()方法具有清除中断状态的功能,即调用方法之后会将中断状态的标志设置为false。

    2、isInterrupted()方法

    我们直接修改上面的代码,用isInterrupted()替换interrupted()

    myThread.interrupt();
    System.out.println("thread state1=" + myThread.isInterrupted());
    System.out.println("thread state2=" + myThread.isInterrupted());
    
    ……
    run: 8003
    run: 8004
    run: 8005
    run: 8006
    run: 8007
    thread state1=true
    thread state2=true
    run: 8008
    run: 8009
    run: 8010
    run: 8011
    run: 8012
    ……
    

    从运行结果可以看到,isInterrupted()方法判断的确实是调用它的线程的中断状态,而且没有清楚标志位,所以两次state都为true。

    总结:

    1. Thread.interrupted()方法:判断当前线程是否处于中断状态,并且具有将状态标志清除为false的功能。
    2. threadObject.isInterrupted()方法:判断调用它的线程是否处于中断状态,不会清除状态标志。

    二、停止线程

    仔细观察前面的测试结果就可以发现,调用interrupt()方法仅仅是在当前线程中打一个停止的标志,并没有真正地停止线程,接下来开始介绍如何真正地停止线程。

    第一种:抛出异常停止线程

    既然interrupt()方法可以给线程打上中断的标志位,那么我们可以通过判断中断状态抛出异常来停止线程。

    public class InterruptedThread {
    
        public static void main(String... args) {
            MyThread myThread = new MyThread();
            myThread.start();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            myThread.interrupt();
        }
    
        public static class MyThread extends Thread {
            @Override
            public void run() {
                super.run();
                try {
                    for (int i = 0; i < 500000; i++) {
                        if (isInterrupted()) {
                            System.out.println("线程已经是中断状态,抛出异常");
                            throw new InterruptedException();
                        }
                        System.out.println("run: " + i);
                    }
                    System.out.println("for语句之后的位置");
                } catch (Exception e) {
                    System.out.println("进入catch块");
                    e.printStackTrace();
                }
            }
        }
    }
    

    运行结果

    run: 268068
    run: 268069
    run: 268070
    run: 268071
    run: 268072
    run: 268073
    run: 268074
    线程已经是中断状态,抛出异常
    进入catch块
    java.lang.InterruptedException
        at test.android.com.testapp.thread.InterruptedThread$MyThread.run(InterruptedThread.java:24)
    

    第二种:当线程处于sleep()状态时执行interrupt()方法

    public class InterruptedThread {
    
        public static void main(String... args) {
            MyThread myThread = new MyThread();
            myThread.start();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("执行interrupt()方法");
            myThread.interrupt();
        }
    
        public static class MyThread extends Thread {
            @Override
            public void run() {
                super.run();
                    try {
                        System.out.println("开始sleep状态");
                        Thread.sleep(30000);
                        System.out.println("结束sleep状态");
                    } catch (InterruptedException e) {
                        System.out.println("进入catch块");
                        e.printStackTrace();
                    }
            }
        }
    }
    

    运行结果

    开始sleep状态
    执行interrupt()方法
    线程处于sleep()状态时被interrupt(),进入catch块
    java.lang.InterruptedException: sleep interrupted
        at java.lang.Thread.sleep(Native Method)
        at test.android.com.testapp.thread.InterruptedThread$MyThread.run(InterruptedThread.java:23)
    

    根据结果显示可以知道:当线程处于sleep()状态时,如果执行interrupt()操作,会抛出java.lang.InterruptedException: sleep interrupted异常,因此可以利用线程的这个特点来停止线程。

    第三种:使用废弃的stop()方法强制停止线程

    public class InterruptedThread {
    
        public static void main(String... args) {
            MyThread myThread = new MyThread();
            myThread.start();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("time1="+System.currentTimeMillis());
            myThread.stop();
        }
    
        public static class MyThread extends Thread {
            @Override
            public void run() {
                super.run();
                try {
                    for (int i = 0; i < 500000; i++) {
                        System.out.println("i=" + i);
                    }
                } catch (ThreadDeath e) {
                    System.out.println("time2="+System.currentTimeMillis());
                    System.out.println("捕捉到ThreadDeath异常");
                    e.printStackTrace();
                }
            }
        }
    }
    

    运行结果

    i=282359
    time1=1571237085770
    i=282360
    i=282361
    i=282362
    i=282363
    i=282364
    i=282365
    i=282366
    i=282367
    i=282368
    i=282369
    i=282370
    i=282371
    i=282372
    i=282373
    i=282374
    i=282375
    i=282376i=282376time2=1571237085770
    捕捉到ThreadDeath异常
    java.lang.ThreadDeath
        at java.lang.Thread.stop(Thread.java:850)
        at test.android.com.testapp.thread.InterruptedThread.main(InterruptedThread.java:14)
    

    运行结果表明,stop()方法确实可以强制终止线程,并且调用stop()方法时会抛出 java.lang.ThreadDeath 异常。

    需要注意的是,使用stop()方法强制停止线程会导致一些额外工作得不到执行。还有一个问题就是它对锁定的对象进行了解锁,也就是释放对象锁,这很可能会导致数据不一致。这些都是导致stop()方法被废弃的原因。

    第四种:直接return线程的run()方法

    与第一种方法类似,我们只是将抛出异常替换为return。

    if (isInterrupted()) {
          System.out.println("线程已经是中断状态,执行return");
          return;
    }
    

    运行结果

    run: 255807
    run: 255808
    run: 255809
    run: 255810
    run: 255811
    run: 255812
    run: 255813
    run: 255814
    run: 255815
    run: 255816
    线程已经是中断状态,执行return
    

    果不其然,达到了停止线程的目的。

    相关文章

      网友评论

        本文标题:学会优雅地停止线程,告别暴力停止线程

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