13 停止线程执行的正确姿势——线程中断
Diego38 43 1

1. 前言

上节讲线程的状态和状态变迁,在线程生命周期内,当一个线程运行结束,就会进入 TERMINATED 终止状态。

当线程长时间处于等待、休眠状态时,他能否被终止掉? Thread.stop API 已经被废弃,正确的终止操作应该是什么?当 Tomcat 进程退出时,运行的线程需要在退出前释放数据库连接,做到优雅关闭,这是怎么做到的?

带着这些疑问,我们学习本节内容 — 线程中断。

2. 中断的定义

每一个线程都有一个中断位属性,默认值为 false。 当一个运行的线程被其他线程操作中断 (thread.interrupt ()) 时,该线程的中断标志属性会设置为 true。

收到中断信号的线程可以通过捕获中断异常 (InterruptedException) 或者检查中断位来判断中断状态,进而可以做出响应,比如释放数据库连接,退出线程或者干脆忽略中断的信号。

就好比你走在大街上,后面有人喊你的名字,你可以停下来回头看一眼,也可以当做没听到。线程中断仅仅传达信号,并不强制目标线程立即终止,收到信号后的操作完全取决于目标线程。

3. 利用中断终止线程

线程中断涉及 Thread API 三个常见的方法:

  • interrupt () 传递中断信号,目标线程可以是当前线程也可以是其他线程
  • isInterrupted () 判断线程是否中断,常用于判断当前线程的中断位标记
  • interrupted () 静态方法,返回是否中断,并且如果被设置了中断位则清理, 否则 什么都不做。

中断响应有两种方式,当做等待或超时等待操作时可以通过捕获中断异常 (InterruptedException) 来响应中断,也可以通过检查中断位来响应中断。接下来我们分别对这两种方式举例说明。

3.1 通过捕获中断异常响应中断

结合下面的代码分析下捕获中断异常响应中断的过程:

public class InterruptedCatchExTest {

   static Thread thread1 = new Thread(() -> {
        while (true) {
            try {
                System.out.println("循环过程中的中断位:" + Thread.currentThread().isInterrupted());
                Thread.sleep(2000); //此处可以是sleep,wait等
            } catch (InterruptedException e) { // 抛出异常之前,中断位被清理,变回false
                System.out.println(e.getMessage());
                System.out.println("当前线程的中断位状态:" + Thread.currentThread().isInterrupted());
                System.out.println("如果当前线程中断为true则清理中断位:" + Thread.interrupted());
                break; //此处退出循环后, 线程也即将终止;当前线程也可以不做break,持续进行。
            }
        }
    },"catch-exception");

    public static void main(String[] args) throws InterruptedException {
        thread1.start();
        Thread.sleep(10_000); //让线程运行一段时间
        thread1.interrupt(); //执行对thread1的中断操作
    }
}

输出结果如下:

循环过程中的中断位:false
循环过程中的中断位:false
循环过程中的中断位:false
循环过程中的中断位:false
循环过程中的中断位:false
sleep interrupted
当前线程的中断位状态:false
如果当前线程中断为true则清理中断位:false

上述代码中,thread1 执行循环操作,每隔 2 秒进行一次打印;Main 线程对 thread1 执行中断操作,thread1 收到中断信号,抛出并捕获了这是因为在抛出 InterruptedException 中断异常,可以看到捕获异常后,调用 Thread 的 API 可以判断当前线程的中断位的值。

thread1 在未抛出异常之前,中断位是 false,抛出并捕获中断异常之后,我们发现打印出的依然是 false,自始至终中断位都是正常。这是因为在抛出 InterruptedException 之前,JVM 会将该线程的中断位清理 (即将中断位设置为 false)

thread1 收到了中断信号,是否退出线程的决定权是交给自己的,执行 break 操作退出循环,线程终止;thread1 也可以不做 break,继续执行循环操作。

以上是通过捕获中断异常的方式响应中断,如果执行过程中不抛出中断异常如何响应中断呢?接下来我们学习通过检查中断位响应中断。

3.2 通过检查中断位响应中断

当对一个线程执行中断后,中断位被设置为 false,我们可以通过 isInterrupted () 判断线程是否中断。接下来就结合代码案例来看一下:

public class CheckInterruptedTest {

    static Thread thread1 = new Thread( () ->{
        while (!Thread.currentThread().isInterrupted()) { //1. 判断中断位状态
            System.out.println("循环体内中断位状态:" + Thread.currentThread().isInterrupted());
        }
        System.out.println("循环体外中断位状态: " + Thread.interrupted() );//中断后进行中断位清理
        System.out.println("对中断位清理后的中断位状态:" + Thread.currentThread().isInterrupted()); //中断位清理之后的状态
        //这里可以进行资源释放
    },"check-interrupted");

    public static void main(String[] args) throws InterruptedException {
       thread1.start();
       Thread.sleep(10);//先执行一段时间
       thread1.interrupt();
    }
}

代码输出如下:

循环体内中断位状态:false
循环体内中断位状态:false
循环体外中断位状态: true
对中断位清理后的中断位状态:false

上述代码中 Main 线程对 thread1 发起中断,thread1 在循环体内通过 Thread.currentThread().isInterrupted() 持续进行中断位检测,当发现中断位为 true 后,退出循环体。

退出循环体后,通过 Thread.interrupted() 清理中断位,中断位属性变为 true。

为什么要清理中断位呢? 前面我们讲到声明了 InterruptedException 的方法遇到阻塞等待时比如 object.wait、thread.sleep、Lock.lockInterruptibly , 都会抛出异常,假设线程收到中断信号,要继续执行,此时中断位已经被设置为 true,当再次遇到声明了 InterruptedException 的方法即将被执行时,这些方法将立即抛出 InterruptedException,而不会真正执行。

以上代码演示了通过自检测中断位来响应中断,进而退出线程,在后续我们讲线程池时还会提起中断操作,线程池是向运行中的线程发生中断指令,进而使得线程池优雅退出的。

4. 总结

image

预览图
评论区

索引目录