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

在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

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

    推荐阅读