使用interrupt停止线程 不能使用volatile标记位

通常情况下我们不会手动的去停止一个线程,而是允许线程运行到结束自然停止,但是某些特殊情况,如用户突然退出程序或程序运行出错时,我们需要提前停止某些正在运行的线程。
 
对于 Java 而言,最正确的停止线程的方式是使用 interrupt。但 interrupt 仅仅起到通知被停止线程的作用。而对于被停止的线程而言,它拥有完全的自主权,它既可以选择立即停止,也可以选择一段时间后停止,也可以选择压根不停止。那么为什么 Java 不提供强制停止线程的能力呢?
Java 希望程序间能够相互通知、相互协作地管理线程,因为如果不了解对方正在做的工作,贸然强制停止线程就可能会造成一些安全的问题,为了避免造成问题就需要给对方一定的时间来整理收尾工作。比如:线程正在写入一个文件,这时收到终止信号,它就需要根据自身业务判断,是选择立即停止,还是将整个文件写入成功后停止,而如果选择立即停止就可能造成数据不完整,不管是中断命令发起者,还是接收者都不希望数据出现问题。
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();
        //假设线程执行到这个方法,并且正在 sleep,此时有线程发送 interrupt 通知试图中断线程,就会立即抛出异常,并清除中断信号。抛出的异常被 catch 语句块捕捉。捕捉到异常的 catch 没有进行任何处理逻辑,相当于把中断信号给隐藏了,这样做是非常不合理的
        //当然,如果我们的线程收到终断请求可以直接停止,我们就可以不去捕获异常,让程序运行过程中直接去抛出异常,中断这个线程。
    
        // 这里至少做两件事:
        // 1、处理线程终止或者抛出终止异常
        // 2、 Thread.currentThread().isInterrupt() ;//这样做的目的是告诉线程后面的操作,这个线程已经终止了
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
        Thread.sleep(5);
        thread.interrupt();
    }
我们一旦调用某个线程的 interrupt() 之后,这个线程的中断标记位就会被设置成 true。每个线程都有这样的标记位,当线程执行时,应该定期检查这个标记位,如果标记位被设置成 true,就说明有程序想终止该线程。回到源码,可以看到在 while 循环体判断语句中,首先通过 Thread.currentThread().isInterrupt() 判断线程是否被中断,随后检查是否还有工作要做。
 
如果 sleep、wait 等可以让线程进入阻塞的方法使线程休眠了,而处于休眠中的线程被中断,那么线程是可以感受到中断信号的,并且会抛出一个 InterruptedException 异常,同时清除中断信号,将中断标记位设置成 false。这样一来就不用担心长时间休眠中线程感受不到中断了,因为即便线程还在休眠,仍然能够响应中断通知,并抛出异常。
要求每一个方法的调用方有义务去处理异常。调用方要不使用 try/catch 并在 catch 中正确处理异常,要不将异常声明到方法签名中。如果每层逻辑都遵守规范,便可以将中断信号层层传递到顶层,最终让 run() 方法可以捕获到异常。而对于 run() 方法而言,它本身没有抛出 checkedException 的能力,只能通过 try/catch 来处理异常。层层传递异常的逻辑保障了异常不会被遗漏,而对 run() 方法而言,就可以根据不同的业务逻辑来进行相应的处理。
之所以不能用volatile标记位声明boolean类型的标识来代替interrupt来停止线程也是这个道理,如果线程发生阻塞了,它将无法执行判断标识位的代码,阻塞无法收到停止线程的通知。线程依然不能停止。
posted @ 2020-01-08 14:37  田海超  阅读(332)  评论(0编辑  收藏  举报