【线程基础】如何正确的停止线程
停止线程有四种方式
方式一 通过volatile标识去判断退出线程#
public class VolatileCanStop implements Runnable{
private static volatile boolean canceled = false;
@Override
public void run() {
int i = 0;
while (!canceled&&i<=1000){
i++;
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" "+i+" execution...");
}
System.out.println(Thread.currentThread().getName()+" is stop...");
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new VolatileCanStop());
thread.start();
TimeUnit.SECONDS.sleep(10);
canceled = true;
}
}
方式二 使用stop()方法去退出线程#
public class StopWay implements Runnable{
@Override
public void run() {
int i = 0;
while (i <= 1000){
i++;
System.out.println(Thread.currentThread().getName()+" "+i+" execution...");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+" is stop...");
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new StopWay());
thread.start();
TimeUnit.SECONDS.sleep(10);
thread.stop();
}
}
方法三 suspend() 和 resume() 方法来暂停和恢复线程#
public class SuspendWay implements Runnable{
@Override
public void run() {
int i = 0;
while (true){
System.out.println(Thread.currentThread().getName()+" "+i+" execution...");
try {
i++;
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new SuspendWay());
thread.start();
TimeUnit.SECONDS.sleep(10);
thread.suspend();
System.out.println(thread.getName()+" pause 5 seconds...");
TimeUnit.SECONDS.sleep(5);
thread.resume();
System.out.println(thread.getName()+" recover...");
}
}
方法四 使用interrupt方法中断线程。#
public class InterruptWay implements Runnable{
@Override
public void run() {
int i = 0;
while (!Thread.currentThread().isInterrupted()&&i<=100){
System.out.println(Thread.currentThread().getName()+" "+i+" execution...");
try {
i++;
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new InterruptWay());
thread.start();
TimeUnit.SECONDS.sleep(10);
thread.interrupt();
}
}
最适合停止线程的方法是哪种呢?
首先我们需要懂一个原则:线程的停止应该是建立在通知和协作上完成,而不是强制停止。
基于这个原则上来看,我们其实就可以排除掉使用 stop() 方法 和 suspend()方法了。
-
在stop方法中,他是属于暴力强制性去停止线程,本质上是不安全的,当他去停止线程的时候会抛出ThreadDeath异常,这可能会导致线程业务流程不完整的情况,强制关闭线程可能会导致我们无法知道子线程是什么时候关闭的,这时如果需要对线程做资源回收等操作时讲无法进行。
-
那suspend()方法呢,这个方法其实只起到了暂停线程的作用,后续还可以通过resume来恢复线程的执行。这样的搭配看起来确实没有什么毛病,但是如果使用不当,很容易造成公共的对象的独占,使得其他线程无法访问公共对象,也就是通过suspend()方法停止的线程是不会释放锁的,这样除了不会释放资源,还可能会导致线程中数据不同步的情况。
其实在Java中,官方已经对stop和suspend还有resume方法做了弃用,提醒我们有更好更安全的方法去使用。
stop和suspend不能用,现在我们就只剩下两种方法,一种是通过volatile标识去判断退出线程还有一种是通过使用interrupt方法中断线程。
乍一看,通过volatile标识去判断退出线程好像还挺靠谱的,似乎也是满足是通过通知和协作的方式来让线程来结束的。那他是否可以用在所有的应用场景上呢?
答案是不能的,在上面举例中,他确实可以使用且不会产生毛病,但是接下来我们看下面这种场景。
生产者
public class Producer implements Runnable {
public volatile boolean canceled = false;
BlockingQueue storage;
public Producer(BlockingQueue storage) {
this.storage = storage;
}
@Override
public void run() {
int num = 0;
try {
while (num <= 100000 && !canceled) {
//如果是50的倍数,则放入仓库
if (num % 50 == 0) {
storage.put(num);
System.out.println(num + "是50的倍数,被放到仓库中了。");
}
num++;
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("生产者结束运行");
}
}
}
消费者
public class Consumer {
BlockingQueue storage;
public Consumer(BlockingQueue storage) {
this.storage = storage;
}
public boolean needMoreNums() {
return !(Math.random() > 0.97);
}
}
Main方法
public class ExecutorMain {
public static void main(String[] args) throws InterruptedException {
ArrayBlockingQueue storage = new ArrayBlockingQueue(8);
Producer producer = new Producer(storage);
Thread producerThread = new Thread(producer);
//开始执行生产者,从100000收集所有是50倍数的数字
producerThread.start();
Thread.sleep(500);
//开始执行消费者
Consumer consumer = new Consumer(storage);
//判断随机数是否大于0.97 大于则跳出,小于则执行消费
while (consumer.needMoreNums()) {
System.out.println(consumer.storage.take() + "被消费了");
Thread.sleep(100);
}
System.out.println("消费者不需要更多数据了。");
//一旦消费不需要更多数据了,我们应该让生产者也停下来,但是实际情况却停不下来
producer.canceled = true;
System.out.println(producer.canceled);
}
}
首先我们来看一下执行的流程,首先是执行生产者,会先从100000个数字中找出是50的倍数,并放入队列中,等待500毫秒后,保障生产者有足够的时间将仓库塞满,当仓库达到容量后就不会在继续塞数据,这个时候生产者就会阻塞,500毫秒后消费者也被创建出来,并通过随机数大于0.97的方式来判断是否需要消费,然后每次消费后休眠100毫秒,当随机数大于0.97时,消费者不再需要数据会跳出循环,之后会将canceled的标记设置为true,并打印输出生产者运行结束。
了解执行流程后发现逻辑好像没有毛病呀,可以是执行几次后会发现,尽管执行到最后已经把canceled设置为true,但生产者依旧没有停止,这是因为在这种情况下,生产者在执行storage.put(num)时发生阻塞,在他被叫醒之前是没有办法进入下一次循环判断canceled的值的,所以在这种情况下用volatile是没有办法将生产者停下来的。
那这个时候就需要通过interrupt来上场了,interrupt方式与volatile标记方式唯一不同的点是即使生产者处于阻塞状态,interrupt仍能感受到中断信号,并做响应处理。
在interrupt使用上,如果interrupt监听到了中断信号,可以通过isInterrupted()方法来判断,或者捕获InterruptedException异常来处理。
这些需要注意的是
- 再抛出异常时,不要生吞异常。
- 如果可以处理此异常,请完成清理工作后退出。
- 不处理此异常,不继续执行任务则需要重新抛出。
- 不处理此异常,继续执行任务,则需要捕获到异常之后恢复中断标记(交由后续的程序检查中断)
总结
在使用多线程中,需要通过interrupt的方法来去优雅的停止线程,因为这样可以遵循以通知和协作的方式去停止线程,这样也可以使我们对线程的生命周期有全面的把控。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix