在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。
总结:
- Thread.interrupted()方法:判断当前线程是否处于中断状态,并且具有将状态标志清除为false的功能。
- 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
果不其然,达到了停止线程的目的。
网友评论