Java学习之InterruptedException异常处理方式

前言

  • 现象描述

    在Java语言的开发工作中,我们经常会碰到这样一类异常–InterruptedException(中断异常)。在绝大多数时候,我们的处理方式无非是catch住它,然后再输出异常信息,更或者是干脆直接忽略它了。那么这是否是一种正确的处理方式呢,要想搞清楚这件事,我们又必须要了解什么是InterruptedException,什么情况下会导致此异常的发生呢?

什么是中断异常

  • 内容简介

    中断代表线程状态,每个线程都关联了一个中断状态,是一个 true 或 false 的 boolean 值,初始值为 false。

  • 中断原理

    Java 中断机制是一种协作机制,也就是说通过中断并不能直接终止另一个线程,而需要被中断的线程自己处理中断

    这好比是家里的父母叮嘱在外的子女要注意身体,但子女是否注意身体,怎么注意身体则完全取决于自己。

    Java 中断模型也是这么简单,每个线程对象里都有一个 boolean 类型的标识(不一定就要是 Thread 类的字段,实际上也的确不是,这几个方法最终都是通过 native 方法来完成的),代表着是否有中断请求(该请求可以来自所有线程,包括被中断的线程本身)。

    例如,当线程 t1 想中断线程 t2,只需要在线程 t1 中将线程 t2 对象的中断标识置为 true,然后线程 2 可以选择在合适的时候处理该中断请求,甚至可以不理会该请求,就像这个线程没有被中断一样。

  • IBM官网定义

    InterruptedException实质上是一个检测异常,它表明有一个阻塞的被中断了,它尝试进行解除阻塞操作并返回地更早一些。中断阻塞方法的操作线程并不是自身线程干的,而是其它线程。而中断操作发生之后,随后会抛出一个InterruptedException,伴随着这个异常抛出的同时,当前线程的中断状态重新被置为false

出现场景

  • 何时抛出

    当阻塞方法收到中断请求的时候就会抛出InterruptedException异常

  • 哪些方法抛出

    类库中的有些类的方法也可能会调用中断,如 FutureTask 中的 cancel 方法,如果传入的参数为 true,它将会在正在运行异步任务的线程上调用 interrupt 方法,如果正在执行的异步任务中的代码没有对中断做出响应,那么 cancel 方法中的参数将不会起到什么效果;

    又如 ThreadPoolExecutor 中的 shutdownNow 方法会遍历线程池中的工作线程并调用线程的 interrupt 方法来中断线程,所以如果工作线程中正在执行的任务没有对中断做出响应,任务将一直执行直到正常结束。

Thread中断相关方法

使用样例

  • 方法作用

    
        /**
        * @author charlesYan
        * @Description: 测试线程中断相关方法
        * @date 2022年03月23日
        */
        public class InterruptedExceptionTest {
    
            public static void main(String[] args) {
                System.out.println("初始中断状态:" + Thread.currentThread().isInterrupted());
                Thread.currentThread().interrupt();
                System.out.println("执行完interrupt方法后,中断状态:" + Thread.currentThread().isInterrupted());
                System.out.println("首次调用interrupted方法返回结果:" + Thread.currentThread().interrupted());
                System.out.println("此时中断状态:" + Thread.currentThread().isInterrupted());
                System.out.println("第二次调用interrupted方法返回结果:" + Thread.currentThread().interrupted());
                System.out.println("此时中断状态:" + Thread.currentThread().isInterrupted());
            }
        }
    
    
    
    
    
  • 输出结果

    
    
        初始中断状态:false
        执行完interrupt方法后,中断状态:true
        首次调用interrupted方法返回结果:true
        此时中断状态:false
        第二次调用interrupted方法返回结果:false
        此时中断状态:false
    
    
    

interrupt()

  • 作用分析

    将线程中断状态设置为true,表明此线程目前是中断状态。此时如果调用isInterrupted方法,将会得到true的结果。

  • 注意事项

    线程中断仅仅是置线程的中断状态位,不会停止线程。需要用户自己去监视线程的状态为并做处理。支持线程中断的方法(也就是线程中断后会抛出interruptedException的方法)就是在监视线程的中断状态,一旦线程的中断状态被置为“中断状态”,就会抛出中断异常

interrupted() 和 isInterrupted()

  • 源码分析

    
        // 检测当前线程是否已经中断,此方法会清除中断状态,也就是说,假设当前线程中断状态为true,第一次调此方法,将返回true,表明的确已经中断了,但是第二次调用后,将会返回false,因为第一次调用的操作已将中断状态重新置为false了。
        public static boolean interrupted() {
            return currentThread().isInterrupted(true);
        }
    
        // 检测当前线程是否已经中断,此方法与上一方法的区别在于此方法不会清除中断状态。
        public boolean isInterrupted() {
            return isInterrupted(false);
        }
    
    
  • 主要区别

    interrupted 是作用于当前线程,isInterrupted 是作用于调用该方法的线程对象所对应的线程(线程对象对应的线程不一定是当前运行的线程:例如我们可以在A线程中去调用B线程对象的isInterrupted方法)。

    
        // ClearInterrupted我们就能知道,这个参数代表是否要清除状态位。如果这个参数为true,说明返回线程的状态位后,要清掉原来的状态位(恢复成原来情况)。这个参数为false,就是直接返回线程的状态位。
        private native boolean isInterrupted(boolean ClearInterrupted);
    
    
    

    这两个方法很好区分,只有当前线程才能清除自己的中断位(对应interrupted()方法)

举例说明

interrupt ≠ 终止操作

  • 样例源码

    
        public class InterruptedExceptionTest {
    
            public static void main(String[] args) throws Exception {
    
                Thread thread = new Thread(new Worker());
                thread.start();
                Thread.sleep(200);
                thread.interrupt();
                System.out.println("Main thead stopped..");
            }
    
            public static class Worker implements Runnable{
    
                @Override
                public void run() {
                    System.out.println("Worker started...");
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        System.out.println("Worker is interrupted:" + Thread.currentThread().isInterrupted());
                    }
                    System.out.println("Worker stopped...");
                }
            }
        }
    
    
    
    
  • 输出结果

    
        Worker started...
        Main thead stopped..
        Worker is interrupted:false
        Worker stopped...
        
    
    
    
  • 结论

    interrupt方法本质上不会进行线程的终止操作的,它不过是改变了线程的中断状态。而改变了此状态带来的影响是,部分可中断的线程方法(比如Object.wait, Thread.sleep)会定期执行isInterrupted方法,检测到此变化,JVM会将线程的中断标志重新设置为false,随后会停止阻塞并抛出InterruptedException异常。总之,interrupt的作用就是需要用户自己去监视线程的状态位并做处理。

    但InterruptedException异常的抛出并不是意味着线程必须得终止,它只是提醒当前线程有中断操作发生了,接下来怎么处理完全取决于线程本身,一般有3种处理方式:

    1. "吞并"异常,当做什么事都没发生过。

    2. 继续往外抛出异常。

    3. 其它方式处理异常(其它处理异常的方式就有很多种了,停止当前线程或者输出异常信息等等)。

中断异常处理

处理方式

中断状态的管理

  • 内容简介

    当可能阻塞的方法声明中有抛出 InterruptedException 则暗示该方法是可中断的,如 BlockingQueue#put、BlockingQueue#take、Object#wait、Thread#sleep 等,如果程序捕获到这些可中断的阻塞方法抛出的 InterruptedException 或检测到中断后,这些中断信息该如何处理?一般有以下两个通用原则:

  • 向上抛出

    如果遇到的是可中断的阻塞方法抛出 InterruptedException,可以继续向方法调用栈的上层抛出该异常,如果是检测到中断,则可清除中断状态并抛出 InterruptedException,使当前方法也成为一个可中断的方法

  • 捕获异常

    若有时候不太方便在方法上抛出 InterruptedException,比如要实现的某个接口中的方法签名上没有 throws InterruptedException,这时就可以捕获可中断方法的 InterruptedException 并通过 Thread.currentThread.interrupt() 来重新设置中断状态。

    一般的代码中,尤其是作为一个基础类库时,绝不应当吞掉中断,即捕获到 InterruptedException 后在 catch 里什么也不做,清除中断状态后又不重设中断状态也不抛出 InterruptedException 等。因为吞掉中断状态会导致方法调用栈的上层得不到这些信息

    当然,凡事总有例外的时候,当你完全清楚自己的方法会被谁调用,而调用者也不会因为中断被吞掉了而遇到麻烦,就可以这么做

中断的响应

  • 常见场景

    有些程序可能一检测到中断就立马将线程终止,有些可能是退出当前执行的任务,继续执行下一个任务……作为一种协作机制,这要与中断方协商好,当调用 interrupt 会发生些什么都是事先知道的:如做一些事务回滚操作,一些清理工作,一些补偿操作等。若不确定调用某个线程的 interrupt 后该线程会做出什么样的响应,那就不应当中断该线程

如何中断线程

作为终止请求

  • 方案描述

    这是最基础中断形式,某些线程非常重要,以至于它们应该不理会中断,而是在处理完抛出的异常之后继续执行,但是更普遍的情况是,一个线程将把中断看作一个终止请求,这种线程的run方法遵循如下形式:

  • 样例源码

    
    
      public void run() {
          try {
            ...
            /*
            * 不管循环里是否调用过线程阻塞的方法如sleep、join、wait,这里还是需要加上
            * !Thread.currentThread().isInterrupted()条件,虽然抛出异常后退出了循环,显
            * 得用阻塞的情况下是多余的,但如果调用了阻塞方法但没有阻塞时,这样会更安全、更及时。
            */
            while (!Thread.currentThread().isInterrupted()&& more work to do) {
              do more work 
            }
          } catch (InterruptedException e) {
            //线程在wait或sleep期间被中断了
          } finally {
            //线程结束前做一些清理工作
          }
      }
    
    
    
    

需要重新设置中断状态

  • 方案描述

    上面是while循环在try块里,如果try在while循环里时,应该在catch块里重新设置一下中断表示位,因为抛出InterruptedException异常后,中断标示位会自动清除。

  • 样例源码

    
    
      public void run() {
        while (!Thread.currentThread().isInterrupted()&& more work to do) {
          try {
            ...
            sleep(delay);
          } catch (InterruptedException e) {
            Thread.currentThread().interrupt();//重新设置中断标示
          }
        }
      }
    
    

底层中断异常处理方式

  • 捕获不处理

    不要在你的底层代码里捕获InterruptedException异常后不处理,会处理不当。

    
      void mySubTask(){
        try{
          sleep(delay);
        }catch(InterruptedException e){}//不要这样做
      }
    
    
    

    如果你不知道抛InterruptedException异常后如何处理,那么你有如下好的建议处理方式:

  • 恢复中断状态

    在catch子句中,调用Thread.currentThread.interrupt()来设置中断状态(因为抛出异常后中断标示会被清除),让外界通过判断Thread.currentThread().isInterrupted()标示来决定是否终止线程还是继续下去。

    
      void mySubTask() {
        ...
        try {
          sleep(delay);
        } catch (InterruptedException e) {
          Thread.currentThread().isInterrupted();
        }
        ...
      }
    
    
    
  • 直接抛出异常

    
    
      void mySubTask() throws InterruptedException {
        ...
        sleep(delay);
        ...
      }
    
    
    
    
  • 小技巧

    如果你不知道如何处理异常,外界有需要知道这个异常,就把他抛出去。

使用中断信号量中断非阻塞状态线程

  • 方案描述

    中断线程最好的,最受推荐的方式是:使用共享变量(shared variable)发出信号,告诉线程必须停止正在运行的任务。线程必须周期性的核查这一变量,然后有秩序地中止任务。

    这里需注意一点的是需将共享变量定义成volatile 类型或将对它的一切访问封入同步的块/方法(synchronized blocks/methods)中

  • 样例源码

    
      class Example2 extends Thread {
        volatile boolean stop = false;// 线程中断信号量
        public static void main(String args[]) throws Exception {
          Example2 thread = new Example2();
          System.out.println("Starting thread...");
          thread.start();
          Thread.sleep(3000);
          System.out.println("Asking thread to stop...");
          // 设置中断信号量
          thread.stop = true;
          Thread.sleep(3000);
          System.out.println("Stopping application...");
        }
        public void run() {
          // 每隔一秒检测一下中断信号量
          while (!stop) {
            System.out.println("Thread is running...");
            long time = System.currentTimeMillis();
            /*
            * 使用while循环模拟 sleep 方法,这里不要使用sleep,否则在阻塞时会 抛
            * InterruptedException异常而退出循环,这样while检测stop条件就不会执行,
            * 失去了意义。
            */
            while ((System.currentTimeMillis() - time < 1000)) {}
          }
          System.out.println("Thread exiting under request...");
        }
      }
    
    
    
    
  • 输出结果

    
    
      Starting thread...
      Thread is running...
      Thread is running...
      Thread is running...
      Thread is running...
      Asking thread to stop...
      Thread exiting under request...
      Stopping application...
    
    
    
    

使用thread.interrupt()中断非阻塞状态线程

  • 方案描述

    上面是中断一个非阻塞状态的线程的常见做法,但对非检测isInterrupted()条件会更简洁。

  • 样例源码

    
      class Example2 extends Thread {
        public static void main(String args[]) throws Exception {
          Example2 thread = new Example2();
          System.out.println("Starting thread...");
          thread.start();
          Thread.sleep(3000);
          System.out.println("Asking thread to stop...");
          // 发出中断请求
          thread.interrupt();
          Thread.sleep(3000);
          System.out.println("Stopping application...");
        }
    
        public void run() {
          // 每隔一秒检测是否设置了中断标示
          while (!Thread.currentThread().isInterrupted()) {
            System.out.println("Thread is running...");
            long time = System.currentTimeMillis();
            // 使用while循环模拟 sleep
            while ((System.currentTimeMillis() - time < 1000) ) {
                // 这里应该短暂的 sleep,避免对 CPU 消耗过大
            }
          }
          System.out.println("Thread exiting under request...");
        }
      }
    
    
    
    
  • 小结

    但是,当线程等待某些事件发生而被阻塞,又会发生什么?

    当然,如果线程被阻塞,它便不能核查共享变量,也就不能停止。这在许多情况下会发生,例如调用Object.wait()、ServerSocket.accept()和DatagramSocket.receive()时,都可能永久的阻塞线程。即使发生超时,在超时期满之前持续等待也是不可行和不适当的,所以,要使用某种机制使得线程更早地退出被阻塞的状态。

使用thread.interrupt()中断阻塞状态线程

  • 方案描述

    Thread.interrupt()方法不会中断一个正在运行的线程。

    这一方法实际上完成的是,设置线程的中断标示位,在线程受到阻塞的地方(如调用sleep、wait、join等地方)抛出一个异常InterruptedException,并且中断状态也将被清除,这样线程就得以退出阻塞的状态。

  • 样例源码

    
      class Example3 extends Thread {
        public static void main(String args[]) throws Exception {
          Example3 thread = new Example3();
          System.out.println("Starting thread...");
          thread.start();
          Thread.sleep(3000);
          System.out.println("Asking thread to stop...");
          thread.interrupt();// 等中断信号量设置后再调用
          Thread.sleep(3000);
          System.out.println("Stopping application...");
        }
    
        public void run() {
          while (!Thread.currentThread().isInterrupted()) {
            System.out.println("Thread running...");
            try {
              /*
              * 如果线程阻塞,将不会去检查中断信号量stop变量,所 以thread.interrupt()
              * 会使阻塞线程从阻塞的地方抛出异常,让阻塞线程从阻塞状态逃离出来,并
              * 进行异常块进行 相应的处理
              */
              Thread.sleep(1000);// 线程阻塞,如果线程收到中断操作信号将抛出异常
            } catch (InterruptedException e) {
              System.out.println("Thread interrupted...");
              /*
              * 如果线程在调用 Object.wait()方法,或者该类的 join() 、sleep()方法
              * 过程中受阻,则其中断状态将被清除
              */
              System.out.println(this.isInterrupted());// false
              //中不中断由自己决定,如果需要真真中断线程,则需要重新设置中断位,如果
              //不需要,则不用调用
              Thread.currentThread().interrupt();
            }
          }
          System.out.println("Thread exiting under request...");
        }
      }
    
    
    
  • 输出结果

    
      Starting thread...
      Thread is running...
      Thread is running...
      Thread is running...
      Asking thead to stop...
      Thread interrupted...
      false
      Thread exiting under request...
      Stopping application...
    
    
    
    
    
    
  • 结果分析

    一旦Example3中的Thread.interrupt()被调用,线程便收到一个异常,于是逃离了阻塞状态并确定应该停止

    上面我们还可以使用共享信号量来替换!Thread.currentThread().isInterrupted()条件,但不如它简洁。

总结

  1. 没有任何语言方面的需求一个被中断的线程应该终止。中断一个线程只是为了引起该线程的注意,被中断线程可以决定如何应对中断

  2. 对于处于sleep,join等操作的线程,如果被调用interrupt()后,会抛出InterruptedException,然后线程的中断标志位会由true重置为false,因为线程为了处理异常已经重新处于就绪状态。

  3. 不可中断的操作,包括进入synchronized段以及Lock.lock(),inputSteam.read()等,调用interrupt()对于这几个问题无效,因为它们都不抛出中断异常。如果拿不到资源,它们会无限期阻塞下去。

  4. 对于Lock.lock(),可以改用Lock.lockInterruptibly(),可被中断的加锁操作,它可以抛出中断异常。等同于等待时间无限长的Lock.tryLock(long time, TimeUnit unit)。

  5. 对于inputStream等资源,有些(实现了interruptibleChannel接口)可以通过close()方法将资源关闭,对应的阻塞也会被放开。

参考链接

  • 遇见InterruptedException异常,怎么办?

    https://blog.csdn.net/Androidlushangderen/article/details/54984681

  • 不学无数——InterruptedException异常处理

    https://www.jianshu.com/p/a8abe097d4ed

  • JCIP-18-thread InterruptedException 中断异常处理及中断机制

    https://houbb.github.io/2019/01/18/jcip-18-thread-interrupt

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值