• 2.Java怎么正确停止线程呢?


    目录

    需求:

    怎么停止呢?

    再说interrrupt:

    为什么 Java 不提供强制停止线程的能力呢?

    如何用 interrupt 停止线程呢?

    sleep 期间能否感受到中断呢?

    正确停止代码

    1、方法签名中抛出异常

    2、再次中断


    一般我们在开发中想要启动线程需要调用 Thread 类的 start() 方法,并在 run() 方法中定义需要执行的任务就好了。

    那么停止就不是那么好玩了,下面就总结瞎怎么停止线程。

    需求:

    通常情况下,我们不会手动停止一个线程,而是允许线程运行到结束,然后让它自然停止。

    但是依然会有许多特殊的情况需要我们提前停止线程,比如:用户突然关闭程序,或程序运行出错重启等。

    在这种情况下,即将停止的线程在很多业务场景下仍然很有价值。尤其是我们想写一个健壮性很好,能够安全应对各种场景的程序时,正确停止线程就显得格外重要。

    但是Java 并没有提供简单易用,能够直接安全停止线程的能力。

    所以搞多线程开发,对我们程序员来说是考验技术得时候了。

    怎么停止呢?

    先说结论:对于 Java 而言,最正确的停止线程的方式是使用 interrupt。

    再说interrrupt:

    interrupt 仅仅起到通知被停止线程的作用。

    而对于被停止的线程而言,它拥有完全的自主权,它既可以选择立即停止,也可以选择一段时间后停止,也可以选择压根不停止。

    这个时候又有问题了。

    为什么 Java 不提供强制停止线程的能力呢?

    因为Java 希望程序间能够相互通知、相互协作地管理线程,因为如果不了解对方正在做的工作,贸然强制停止线程就可能会造成一些安全的问题,为了避免造成问题就需要给对方一定的时间来整理收尾工作。

    比如:

    线程正在写入一个文件,这时收到终止信号,它就需要根据自身业务判断,是选择立即停止,还是将整个文件写入成功后停止,而如果选择立即停止就可能造成数据不完整,不管是中断命令发起者,还是接收者都不希望数据出现问题。

    如何用 interrupt 停止线程呢?

    源码:

    1. while (!Thread.currentThread().isInterrupted() && more work to do) {
    2. do more work
    3. }

    可以看到在 while 循环体判断语句中,首先通过 Thread.currentThread().isInterrupt() 判断线程是否被中断,随后检查是否还有工作要做。

    && 逻辑表示只有当两个判断条件同时满足的情况下,才会去执行下面的工作。

    具体例子:

    1. public class StopThread implements Runnable {
    2. @Override
    3. public void run() {
    4. int count = 0;
    5. while (!Thread.currentThread().isInterrupted() && count < 1000) {
    6. System.out.println("count = " + count++);
    7. }
    8. }
    9. public static void main(String[] args) throws InterruptedException {
    10. Thread thread = new Thread(new StopThread());
    11. thread.start();
    12. Thread.sleep(5);
    13. thread.interrupt();
    14. }
    15. }
    1. 首先判断线程是否被中断,然后判断 count 值是否小于 1000。
    2. 这个线程的工作内容很简单,就是打印 0~999 的数字,每打印一个数字 count 值加 1,可以看到,线程会在每次循环开始之前,检查是否被中断了。

    3. 接下来在 main 函数中会启动该线程,然后休眠 5 毫秒后立刻中断线程,该线程会检测到中断信号,于是在还没打印完1000个数的时候就会停下来,这种就属于通过 interrupt 正确停止线程的情况。

    sleep 期间能否感受到中断呢?

    先说结论:线程还在休眠,仍然能够响应中断通知,并抛出异常。

    案例:

    1. public static void main(String[] args) throws InterruptedException {
    2. Runnable runnable = () -> {
    3. int num = 0;
    4. try {
    5. while (!Thread.currentThread().isInterrupted() && num <= 1000) {
    6. System.out.println(num);
    7. num++;
    8. Thread.sleep(1000000);
    9. }
    10. } catch (InterruptedException e) {
    11. e.printStackTrace();
    12. }
    13. };
    14. Thread thread = new Thread(runnable);
    15. thread.start();
    16. Thread.sleep(5);
    17. thread.interrupt();
    18. }
    19. }

    代码意思是:

    如果线程在执行任务期间有休眠需求,也就是每打印一个数字,就进入一次 sleep ,而此时将 Thread.sleep() 的休眠时间设置为 1000 秒钟。

    主线程休眠 5 毫秒后,通知子线程中断,此时子线程仍在执行 sleep 语句,处于休眠中。

    所以如果不能中断就会带来严重的问题,因为响应中断太不及时了。正因为如此,Java 设计者在设计之初就考虑到了这一点。

    如果 sleep、wait 等可以让线程进入阻塞的方法使线程休眠了,而处于休眠中的线程被中断,那么线程是可以感受到中断信号的,并且会抛出一个 InterruptedException 异常,同时清除中断信号,将中断标记位设置成 false。

    1. java.lang.InterruptedException: sleep interrupted
    2. at java.base/java.lang.Thread.sleep(Native Method)
    3. at com.qax.tsgz.toolbox.input.domain.operation.impl.StopThread.lambda$main$0(StopThread.java:33)
    4. at java.base/java.lang.Thread.run(Thread.java:834)

    正确停止代码

    1、方法签名中抛出异常

    先说一个反例:

    在方法中使用 try/catch 或在方法签名中声明 throws  InterruptedException。

    1. void subTas() {
    2. try {
    3. Thread.sleep(1000);
    4. } catch (InterruptedException e) {
    5. // 在这里不处理该异常是非常不好的
    6. }
    7. }

    如上面的代码所示,catch 语句块里代码是空的,它并没有进行任何处理。

    假设线程执行到这个方法,并且正在 sleep,此时有线程发送 interrupt 通知试图中断线程,就会立即抛出异常,并清除中断信号。抛出的异常被 catch 语句块捕捉。

    但是,捕捉到异常的 catch 没有进行任何处理逻辑,相当于把中断信号给隐藏了,这样做是非常不合理的。

    正确方式:

    方法签名中抛出异常。

    1. void subTask2() throws InterruptedException {
    2. Thread.sleep(1000);
    3. }

    正如代码所示,要求每一个方法的调用方有义务去处理异常。

    调用方要不使用 try/catch 并在 catch 中正确处理异常,要不将异常声明到方法签名中。

    如果每层逻辑都遵守规范,便可以将中断信号层层传递到顶层,最终让 run() 方法可以捕获到异常。

    而对于 run() 方法而言,它本身没有抛出 checkedException 的能力,只能通过 try/catch 来处理异常。层层传递异常的逻辑保障了异常不会被遗漏,而对 run() 方法而言,就可以根据不同的业务逻辑来进行相应的处理。

    2、再次中断

    在 catch 语句块中调用 Thread.currentThread().interrupt() 函数

    1. private void reInterrupt() {
    2. try {
    3. Thread.sleep(2000);
    4. } catch (InterruptedException e) {
    5. Thread.currentThread().interrupt();
    6. e.printStackTrace();
    7. }
    8. }

    因为如果线程在休眠期间被中断,那么会自动清除中断信号。如果这时手动添加中断信号,中断信号依然可以被捕捉到。

    这样后续执行的方法依然可以检测到这里发生过中断,可以做出相应的处理,整个线程可以正常退出。

    需要注意的是:

    我们需要注意,我们在实际开发中不能盲目吞掉中断。

    我们需要注意,我们在实际开发中不能盲目吞掉中断。

    我们需要注意,我们在实际开发中不能盲目吞掉中断。

    如果不在方法签名中声明,也不在 catch 语句块中再次恢复中断,而是在 catch 中不作处理,我们称这种行为是【屏蔽了中断请求】

    如果我们盲目地屏蔽了中断请求,会导致中断信号被完全忽略,最终导致线程无法正确停止。

  • 相关阅读:
    学习基于html和JavaScript的滑动图片拼图验证源码
    Springboot毕设项目个性化健康饮食推荐平台79no0(java+VUE+Mybatis+Maven+Mysql)
    2022-06-27 网工进阶(十二)IS-IS-开销类型、开销计算、LSP的处理机制、路由撤销、路由渗透
    E. 矩阵第k大
    从rocketmq入手,解析各种零拷贝的jvm层原理
    什么是报表?分析报表在零售行业中的应用,并推荐“免费高质量”的报表工具
    Kubernetes:(七)优化大法(江湖失传已久的武林秘籍)
    浏览器交互:Cookies、事件、浏览历史
    【代码精读】optee中如何添加一个外设
    关于RocketMQ那些你可能不知道的性能优化!
  • 原文地址:https://blog.csdn.net/daohangtaiqian/article/details/127926250