回答
在 Java 中要终止一个正在运行的线程,有三种方法:
- 使用退出标志。使用一个标志变量来控制线程是否正常退出任务。线程的主循环不断检测这个变量,当变量标志为停止时,线程安全退出。
- 利用中断机制。调用线程的
interrupt()
来控制线程中断。 - 强制退出。调用线程的
stop()
强行终止线程,不推荐这种方法,
详解
使用退出标志
当我们使用轮询或者调用第三方库失败需要不断重试时,这些类似的场景大部分都存在类似一个这样的循环体 while(true) { ... }
,一般这种场景就比较适合实用退出标志来结束线程的运行。
具体方法是:设置一个 volatile 修饰的共享变量,当变量为 true 时线程正常运行,当需要中断时,将该共享变量设置为 false。代码如下:
public class MyThread extends Thread {
volatile Boolean flag = true;
@Override
public void run() {
while (flag) {
System.out.println("当前运行线程为:" +Thread.currentThread().getName() + " - 运行");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("当前运行线程为:" +Thread.currentThread().getName() + " - 退出线程");
}
}
测试类:
public class InterruptTest {
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
myThread.start();
// 等待线程完全运行
TimeUnit.SECONDS.sleep(2);
System.out.println("线程状态:" + myThread.getState().name());
TimeUnit.SECONDS.sleep(5);
myThread.flag = false;
// 等待线程完全退出
TimeUnit.SECONDS.sleep(3);
System.out.println("线程状态:" + myThread.getState().name());
TimeUnit.SECONDS.sleep(5);
}
}
执行结果:
- 1、线程状态
TIMED_WAITING
。因为线程在sleep(2)
。 - 2、线程状态
TERMINATED
,说明线程已执行完run()
退出来了。
这种方式是一种比较优雅的方式,但是需要注意变量的类型。
使用中断机制
有些线程因为 I/O 操作,或者调用了sleep()
、wait()
等类似方法阻塞时,这个时候由于我们线程处于不可运行状态,就无法通过使用退出标志来退出线程了。这个时候我们就可以利用 Thread 的 interrupt()
。该方法可以让一个被阻塞的线程抛出一个中断异常 InterruptedException
,我们可以捕获该异常,然后退出线程。
public class MyThread extends Thread {
@Override
public void run() {
while (true) {
System.out.println("当前运行线程为:" +Thread.currentThread().getName() + " - 运行");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
System.out.println("当前运行线程为:" +Thread.currentThread().getName() + " - 抛出中断异常,退出线程");
return;
}
}
}
}
public class InterruptTest {
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
myThread.start();
// 等待线程完全运行
TimeUnit.SECONDS.sleep(2);
System.out.println("线程状态:" + myThread.getState().name());
TimeUnit.SECONDS.sleep(5);
// 中断线程
myThread.interrupt();
// 等待线程完全退出
TimeUnit.SECONDS.sleep(3);
System.out.println("线程状态:" + myThread.getState().name());
TimeUnit.SECONDS.sleep(5);
}
}
运行结果:
当一个线程调用 interrupt()
时,它并不会立刻中断,而是设置了这个线程的中断标志,线程具体什么时候中断由线程自己决定。利用这个特性,其实我们也可以实现退出标志那样的效果。
在 while(){...}
中我们可以调用 Thread.currentThread().isInterrupted()
或者 Thread.interrupted()
来判断线程是否已中断,如果中断了则退出线程,否则继续运行。
public class MyThread extends Thread {
@Override
public void run() {
int i = 1;
while (true) {
if (this.isInterrupted()) {
System.out.println("当前运行线程为:" +Thread.currentThread().getName() + " - 线程中断,退出");
break;
}
System.out.println("当前运行线程为:" +Thread.currentThread().getName() + " - 线程运行中,i =" + i++);
}
}
}
Thread.currentThread().isInterrupted()
和 Thread.interrupted()
两者都会返回中断标识,但是 Thread.interrupted()
会清理清除中断状态,而 Thread.currentThread().isInterrupted()
则不会清理中断状态。对于是否清理中断状态则根据我们的业务需求来做判断,如果我们希望我们后面的业务代码不受中断状态的影响而继续执行任务,这里推荐使用 Thread.interrupted()
清理中断状态。
使用 stop() 强制退出
一般不推荐采用这种方案,因为stop()
会强制终止一个线程,目前已被废弃。使用它会产生一些很严重的问题:
- 不安全和不可控制的终止:
stop()
会立刻强制终止线程的运行,无论该线程是否在执行关键任务,是否持有锁,是否是在进行资源的清理工作,通通强制终止,这就会导致线程可能处于不一致的状态,如果资源在清理,你终止就可能会导致资源泄露。 - 无法释放锁资源:如果线程持有锁,你将其终止了,锁资源得不到正确的释放,其他线程就会获取不到锁,甚至会产生死锁。
- 不可靠的异常抛出:调用
stop()
时,线程会立即抛出一个ThreadDeath
。ThreadDeath
是一个 Error,一般来说我们都不会显示捕获它。
所以 stop()
是一个不安全、不可控制、容易引发问题的方法,在应用程序中不推荐。
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。