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

目录

需求:

怎么停止呢?

再说interrrupt:

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

如何用 interrupt 停止线程呢?

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

正确停止代码

1、方法签名中抛出异常

2、再次中断


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

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

需求:

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

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

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

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

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

怎么停止呢?

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

再说interrrupt:

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

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

这个时候又有问题了。

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

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

比如:

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

如何用 interrupt 停止线程呢?

源码:

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

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

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

具体例子:

public class StopThread implements Runnable {
 
    @Override
    public 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);
        thread.interrupt();
    }
}
  1. 首先判断线程是否被中断,然后判断 count 值是否小于 1000。
  2. 这个线程的工作内容很简单,就是打印 0~999 的数字,每打印一个数字 count 值加 1,可以看到,线程会在每次循环开始之前,检查是否被中断了。

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

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

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

案例:

    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = () -> {
            int num = 0;
            try {
                while (!Thread.currentThread().isInterrupted() && num <= 1000) {
                    System.out.println(num);
                    num++;
                    Thread.sleep(1000000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
        Thread.sleep(5);
        thread.interrupt();
    }
}

代码意思是:

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

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

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

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

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

正确停止代码

1、方法签名中抛出异常

先说一个反例:

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

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

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

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

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

正确方式:

方法签名中抛出异常。

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

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

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

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

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

2、再次中断

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

private void reInterrupt() {
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        e.printStackTrace();
    }
}

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

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

需要注意的是:

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

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

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

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

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

飞四海

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值