如何正确的停止线程

如何正确的停止线程

1.原理介绍

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

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

在这种情况下,即将停止的线程在很多业务场景下仍然很有价值。尤其是我们想写一个健壮性很好,能

够安全应对各种场景的程序时,正确停止线程就显得格外重要。但是 Java 并没有提供简单易用,能够

直接安全停止线程的能力。

**对于 Java 而言,最正确的停止线程的方式是使用 interrupt。**但 interrupt 仅仅起到通知被停止线程的

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

后停止,也可以选择压根不停止。那么为什么 Java 不提供强制停止线程的能力呢?Java 希望程序间能够相互通知、相互协作地管理线程,因为如果不了解对方正在做的工作,贸

然强制停止线程就可能会造成一些安全的问题,为了避免造成问题就需要给对方一定的时间来整理收尾工作。

2.如何用 interrupt 停止线程

while (!Thread.currentThread().isInterrupted() && more work to do) {
do more work
}
package com.moon.threads;

/**
* 线程停止
*/
public class StopThread implements Runnable {
   @Override
   public void run() {
       int num =0;
       while (!Thread.currentThread().isInterrupted()&& num<5){
           num++;
           System.out.printf("num=="+num);
       }
   }


   public static void main(String[] args) throws InterruptedException {
       Thread thread = new Thread(new StopThread());
       thread.start();
       Thread.sleep(2);
       thread.interrupt();
   }

}

​ 执行结果如下:

num==1
Process finished with exit code 0

这个示例用线程的interrupt,优雅的停止了线程。这个示例的目的是打印输出1-5,当线程休眠2ms后,执行interrupt,然后到第二次循环发现了中断表示,则停止了线程。

3.者休眠过长时间会导致interrupt失效,如下:

package com.moon.threads;

/**
 * 线程停止
 */
public class SleepThreadTest implements Runnable {
    @Override
    public void run() {
        int num =0;
        while (!Thread.currentThread().isInterrupted()&& num<5){
            num++;
            System.out.printf("num=="+num);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }


    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new SleepThreadTest());
        thread.start();
        Thread.sleep(2);
        thread.interrupt();
    }

}

执行结果:
在这里插入图片描述

如果 sleep、wait 等可以让线程进入阻塞的方法使线程休眠了,而处于休眠中的线程被中断,那么线程

是可以感受到中断信号的,并且会抛出一个 InterruptedException 异常,同时清除中断信号,将中断

标记位设置成 false。这样一来就不用担心长时间休眠中线程感受不到中断了,因为即便线程还在休

眠,仍然能够响应中断通知,并抛出异常。

在实际开发中肯定是团队协作的,不同的人负责编写不同的方法,然后相互调用来实现整个业务的逻

辑。那么如果我们负责编写的方法需要被别人调用,同时我们的方法内调用了 sleep 或者 wait 等能响

应中断的方法时,仅仅 catch 住异常是不够的 。需要在异常catch中再次打断 从而真正停止线程。

如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zmDMwN3b-1669385950210)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20221125215403733.png)]

错误的停止方法

  1. stop()
  2. suspend() –导致死锁
  3. resume()–导致死锁

这些方法已经被 Java直接标记为 @Deprecated。是因为 stop() 会直接把线程停止,这样就没有给线程足够的时间来处理想要在停止前保存数据的逻辑,任务戛然而止,会导致出现数据完整性等问题。

假设线程 A 调用了 suspend() 方法让线程 B 挂起,线程 B 进入休眠,而线程 B 又刚好持有一把锁,此

时假设线程 A 想访问线程 B 持有的锁,但由于线程 B 并没有释放锁就进入休眠了,所以对于线程 A 而

言,此时拿不到锁,也会陷入阻塞,那么线程 A 和线程 B 就都无法继续向下执行。

volatile 修饰标记位适用的场景

package com.moon.threads;

/**
 * Volatile 停止线程的正确方式
 */
public class VolatileCanStop implements  Runnable{

    private volatile boolean canceled = false;
    @Override
    public void run() {
        int num = 0;
        try {
            while (!canceled && num <= 10000) {
                if (num % 5 == 0) {
                    System.out.println(num + "是5的倍数。");
                }
                num++;
                Thread.sleep(1);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        VolatileCanStop r = new VolatileCanStop();
        Thread thread = new Thread(r);
        thread.start();
        Thread.sleep(1000);
        r.canceled = true;
    }

}

volatile 修饰标记位不适用的场景

接下来我们就用一个生产者 / 消费者模式的案例来演示为什么说 volatile 标记位的停止方法是不完美

的。下面来看下 main 函数,首先创建了生产者 / 消费者共用的仓库 BlockingQueue storage,仓库容量是

8,并且建立生产者并将生产者放入线程后启动线程,启动后进行 500 毫秒的休眠,休眠时间保障生产

者有足够的时间把仓库塞满,而仓库达到容量后就不会再继续往里塞,这时生产者会阻塞,500 毫秒后

消费者也被创建出来,并判断是否需要使用更多的数字,然后每次消费后休眠 100 毫秒,这样的业务逻

辑是有可能出现在实际生产中的。

当消费者不再需要数据,就会将 canceled 的标记位设置为 true,理论上此时生产者会跳出 while 循

环,并打印输出 “生产者运行结束”。

然而结果却不是我们想象的那样,尽管已经把 canceled 设置成 true,但生产者仍然没有停止,这是因

为在这种情况下,生产者在执行 storage.put(num) 时发生阻塞,在它被叫醒之前是没有办法进入下一

次循环判断 canceled 的值的,所以在这种情况下用 volatile 是没有办法让生产者停下来的,相反如果

用 interrupt 语句来中断,即使生产者处于阻塞状态,仍然能够感受到中断信号,并做响应处理。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值