上一篇讲了如何等待子线程运行结束,原文https://blog.csdn.net/qq_33591903/article/details/108496110。
本篇讲讲如何终止子线程
package com.qcy.testStopThread;
/**
* @author qcy
* @create 2020/09/16 09:40:34
*/
public class Main1 {
static class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100000; i++) {
System.out.println(i);
}
}
}
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
myThread.start();
//保证子线程进入运行状态,避免还没运行就被终止
Thread.sleep(100);
//暴力停止子线程
myThread.stop();
}
}
输出如下:
可以看得出,线程确实被终止了。
但是,这个是一个被废弃掉的方法,Stop方法上的注释如下:
从上面的注释,我们可以得到以下的信息:
- Stop方法时被废弃掉的方法,不推荐使用
- 使用Stop方法,会一直向上传播ThreadDeath异常,从而使得目标线程解锁所有锁住的监视器,即释放掉所有的对象锁。使得之前被锁住的对象得不到同步的处理,因此可能会造成数据不一致的问题。
- 注释中建议我们取代Stop方法,可以增加一个变量。目标线程周期性地检查这个变量。如果变量在某个时间指示线程终止,则目标线程将以有序的方式从run方法中返回。
- 当然,如果目标线程长时间进行等待,则可以使用中断的方式来终止线程。
因此,我们打算在下面的方法中,使用变量或中断的方式来终止线程。
通过指定一个条件变量,外部线程(比如是主线程)可以控制该变量,内部线程(比如子线程)在内部循环检查该变量。为了保证条件变量在内存中的可见性,我们使用volatile修饰它。有关volatile是如何保证可见性的,可以参考我的另外一篇文章多线程之内存可见性
package com.qcy.testStopThread;
/**
* @author qcy
* @create 2020/09/16 10:30:30
*/
public class Main2 {
static class MyThread extends Thread {
//条件变量
private volatile boolean stop = false;
private int i = 0;
public void finish() {
stop = true;
}
@Override
public void run() {
//循环检查条件变量
while (!stop) {
//业务代码
i++;
System.out.println(i);
}
}
}
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
myThread.start();
//保证子线程进入运行状态,避免还没运行就被终止
Thread.sleep(100);
//修改条件变量为false
myThread.finish();
}
}
可以看得出,如果我们的业务要求在每一次循环结束后去检查条件变量,那么以上的写法是没有问题的。
但是,以上方式的即时性不高,如果我们的业务代码有等待操作,比如sleep与wait操作,那么如何在这些操作中就终止线程呢?
package com.qcy.testStopThread;
/**
* @author qcy
* @create 2020/09/16 11:51:30
*/
public class Main3 {
static class MyThread extends Thread {
//条件变量
private volatile boolean stop = false;
private int i = 0;
public void finish() {
stop = true;
this.interrupt();
}
@Override
public void run() {
//循环检查条件变量
while (!stop) {
//业务代码
i++;
System.out.println(i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
System.out.println("线程在sleep期间被打断了");
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
myThread.start();
//保证子线程进入运行状态,避免还没运行就被终止
Thread.sleep(1000);
//停止子线程
myThread.finish();
}
}
输出:
interrupt方法用来设置线程的中断状态,如果目标线程正阻塞于wait、sleep等方法时,首先会清除目前线程的中断状态,然后抛出java.lang.InterruptedException异常。
可以看得出,子线程确实被终止掉了。
能不能使用Thread.isInterrupted()来代替条件变量呢?
我们将代码稍微改造一下,使用Thread.isInterrupted()用来获取目标线程是否处于中断状态。
package com.qcy.testStopThread;
/**
* @author qcy
* @create 2020/09/16 14:03:00
*/
public class Main4 {
static class MyThread extends Thread {
private int i = 0;
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
//业务代码
i++;
System.out.println(i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
System.out.println("线程在sleep期间被打断了");
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
myThread.start();
//保证子线程进入运行状态,避免还没运行就被终止
Thread.sleep(1000);
//停止子线程
myThread.interrupt();
}
}
观察输出:
什么情况,打断了为什么还能继续输出?
在上面其实已经说过了,interrupt方法用来设置线程的中断状态,如果目标线程正阻塞于wait、sleep等方法时,首先会清除目前线程的中断状态,然后抛出java.lang.InterruptedException异常。
目标线程正在进行第11次循环,进入了sleep操作中,此时收到了主线程的interrupt操作,目标线程拥有了中断状态,则先清除中断状态,然后抛出异常,下一次循环检测Thread.currentThread().isInterrupted()时,isInterrupted依然返回false,从而继续进行循环操作。
那怎么停止当前线程呢,很简单,处理异常时,再次打断即可,代码如下:
package com.qcy.testStopThread;
/**
* @author qcy
* @create 2020/09/16 14:03:00
*/
public class Main4 {
static class MyThread extends Thread {
private int i = 0;
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
//业务代码
i++;
System.out.println(i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
System.out.println("线程在sleep期间被打断了");
e.printStackTrace();
//再次打断,设置中断标志,则之后的isInterrupted为true
this.interrupt();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
myThread.start();
//保证子线程进入运行状态,避免还没运行就被终止
Thread.sleep(1000);
//停止子线程
myThread.interrupt();
}
}
输出如下:
可以看得到,子线程被终止掉了。
使用条件变量+中断的方式是终止线程比较优雅的方式,具有一定的即时性。中断仅仅是一种协作机制,是需要被中断的线程自己去处理中断的。