如何正确停止线程
创始人
2024-05-30 15:06:03
0

本文概要:
介绍如何去正确停止一个线程
为什么用 volatile 标记的停止方法可能是错误的?—生产者消费者

为什么不强制停止?

你在学习 stop 方法的时候可能会看到,stop 会让直接停止线程.
但是会发生哪些不好的事情呢, 比如说, 我在写入一个文件, 如果线程突然停止了, 文件输入输出流关闭了吗? 再比如银行系统正在处理 A 给 B 的转账, 如果我们突然把他停止了咋办.
所以说, 很多时候即使是基于特殊情况, 我们也希望让线程把关键的步骤走完再停止, 就像谁愿意去处理离职同事之前的屎山代码呢.

正确的做法是通知, 协作

对于 Java 而言, 我们应该去通知, 也就是使用 interrupt, 他起到的是通知的作用, 对于被通知停止的线程, 它拥有自主权, 他会在合适的时候去停止.
这么做的原因上面也大致谈了谈, 某些业务一定要合理的结束才行, 有始有终, 毕竟谁都不希望数据写一半没有了吧, 这会引起很多问题.

如何去停止线程

public class StopThread implements Runnable {@Overridepublic void run() {int count = 0;while (!Thread.currentThread().isInterrupted() && count < 1000) {System.out.println("count = " + count++);}}public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(new StopThread());thread.start();Thread.sleep(5);// 这里把开启的那个线程状态设置为 interruptthread.interrupt();}
}

每个线程都有标志位, 当我们在其他线程内, 如果持有此线程的对象, 是可以对他进行中断标志位进行设置, 那个线程在 <运行到>这一行代码的时候判断出状态被中断, 然后我们自己手动去让他跳出循环即可.

阻塞, 等待情况分析

有没有注意到上面的三个字 <运行到> ,那么如果是等待或者是阻塞的情况, 就不会运行这一行代码了啊, 还怎么去让他停止

阻塞和等待代表了什么

阻塞: 没有拿到 synchronized 的锁
等待: sleep, wait, join 等

这些方法都会让线程进入一个无法执行代码的状态, 无法判断标志位, 无法通过我们预留的安全通道逃跑, 我们只能用别的办法了.
阻塞和等待都不是陷入一种"死亡"状态, 他们可以感知信号, 所以 Java 开发者是这么去解决阻塞和等待情况下的停止线程问题, 当我们尝试去设置状态的时候, 他们就会抛出错误.

public class SleepThreadExample {public static void main(String[] args) {Thread thread = new Thread(() -> {try {System.out.println("Sleeping for 5 seconds...");Thread.sleep(5000);System.out.println("Woke up from sleep.");} catch (InterruptedException e) {System.out.println("Thread was interrupted while sleeping.");// 这里输出一下线程的状态看看是不是被中断了 System.out.println(Thread.currentThread().isInterrupted());// 这行非常重要Thread.currentThread().interrupt();}});thread.start();try {Thread.sleep(2000);thread.interrupt();} catch (InterruptedException e) {e.printStackTrace();}}
}

上面线程在休眠的时候被设置中断标志位了, 这里要注意一点!, sleep 会让线程休眠, 而此时让其中断, 会自动清楚中断信号. 所以说在使用 sleep 的时候, 如果要中断, 一定要注意设置中断标志位. 如果不设置, 那么那个捕获到 try/catch 实际上不会"告诉其他人", 因为中断标志被设置成 false 了.

注意:sleep()方法不会释放锁资源,但是会释放CPU资源。

延申

上面我们使用的方法是, 使用 try/catch 方法去捕捉异常, 如果线程运行的方法是很多个方法, 方法调用方法, 此时该怎么结束.
就比如说我们会调用同事写的代码, 他是用 try/catch 的, 还是用 Exception 的呢?

我们在真实设计的时候, 应该要注意, 线程 run 方法调用的方法是否是 tcy/catch, 因为我们的 run 方法里面也有很多重要的业务, 假如子方法处理的异常, 然后直接抛出运行时异常, 也就是说子方法出错了, 没有告诉 run 方法出错了, run 方法就有可能很多业务还没有处理.

run ()方法, 他不能抛出异常, 只能去通过 try/catch, 而 run ()方法要想控制自己调用的方法, 以及处理可能发生的异常, 最好是能够接收子方法 throw 的异常. 当然如果子方法能够合理的处理异常, 保证异常不会被遗漏, 那么也是可以自己做出处理的, 但是前提是不会影响到 run()的业务处理.
<无论你有没有抛出异常,最重要的应该是,不能去遗漏他,不能屏蔽中断请求>

错误的几种停止方法

比如说 stop (): 会把线程直接停止, 没有给线程足够的时间去保存关键步骤和数据. 想想线程切换的时候, 都会进行线程的上下文的数据保存
suspend (): 不会释放锁, 就是占着茅坑不拉屎, 后面的人只能干等着. 这"河里"吗, 只有当 resumet 的时候才会释放

volatile 用于停止

还记得刚才, 我说 Thread.currentThtread ().interrupt ()会在代码 <运行时>被判断, 然后我们可以手动预留一个安全通道, 让他正确的退出.
volatile 也能用于设置标识位, 但是时什么原理呢? 还是运行时判断, 也就是说阻塞, 等待都不行.

某些情况下, 是可以使用 volatile 来用于停止的, 但是当阻塞的情况下是不适合的.
阻塞的情况, 不止 wait ()和 sleep ()如果是阻塞队列呢?
而合适的情况下, 你可以参考

	public void run() {int count = 0;while (!Thread.currentThread().isInterrupted() && count < 1000) {System.out.println("count = " + count++);}}

把这里的! Thread.currentThread().isInterrupted()设置成一个 volatile 变量即可

看看不合适的情况

public class Controller {public static void main(String[] args) throws InterruptedException {BlockingQueue blockingQueue = new ArrayBlockingQueue<>(8);Customer customer = new Customer(blockingQueue);Producer producer = new Producer(blockingQueue);Thread thread = new Thread(producer);thread.start();Thread.sleep(1000);// 这里的 need 是个一个概率问题,也就是说,假如等到消费者不拿了,这里就退出了,然后进入下面去停止生产者,可是生产者在阻塞队列堵住了,无法检测标志while (customer.need()){System.out.println(customer.getStorage().take() + "被消费");Thread.sleep(100);}System.out.println("不需要数据了");producer.cancer = true;}
}
public class Producer implements Runnable{volatile  boolean cancer = false;private BlockingQueue storage;public Producer(BlockingQueue storage) {this.storage = storage;}@Overridepublic void run() {int nums = 0;while(nums <= 100000 && !cancer){if (nums%50 == 0){try {// 假如这里满了,然后就会阻塞住,就无法进入下一个循环来检测 cancerstorage.put(nums);System.out.println("加入到仓库");} catch (InterruptedException e) {throw new RuntimeException(e);}}nums++;}}}
public class Customer {private static BlockingQueue storage;public Customer(BlockingQueue storage) {this.storage = storage;}public boolean need(){if (Math.random() > 0.97){return false;}else {return true;}}public BlockingQueue getStorage(){return storage;}
}

结果:


1000 被消费
加入到仓库
不需要数据了

然后就停止了, 因为生产者阻塞了, 却没有停止. 程序也没有结束

相关内容

热门资讯

监控摄像头接入GB28181平... 流程简介将监控摄像头的视频在网站和APP中直播,要解决的几个问题是:1&...
Windows10添加群晖磁盘... 在使用群晖NAS时,我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过...
protocol buffer... 目录 目录 什么是protocol buffer 1.protobuf 1.1安装  1.2使用...
在Word、WPS中插入AxM... 引言 我最近需要写一些文章,在排版时发现AxMath插入的公式竟然会导致行间距异常&#...
【PdgCntEditor】解... 一、问题背景 大部分的图书对应的PDF,目录中的页码并非PDF中直接索引的页码...
修复 爱普生 EPSON L4... L4151 L4153 L4156 L4158 L4163 L4165 L4166 L4168 L4...
Fluent中创建监测点 1 概述某些仿真问题,需要创建监测点,用于获取空间定点的数据࿰...
educoder数据结构与算法...                                                   ...
MySQL下载和安装(Wind... 前言:刚换了一台电脑,里面所有东西都需要重新配置,习惯了所...
MFC文件操作  MFC提供了一个文件操作的基类CFile,这个类提供了一个没有缓存的二进制格式的磁盘...