InterruptException通俗易懂地详解

有一个异常,网上都在描述现象,没有人深究其原因以及给出解决方案:

nested exception is java.sql.SQLException: interrupt

单刀直入,这是数据库驱动抛出的线程异常,这是线程的问题

如果读者无心探索原理,只想知道解决方案,可以直接索引到目录的结论中。

目录

起因

为什么需要InterruptedException?

InterruptedException的应用

结论 


起因

如果我们希望线程等待1秒,通常我们会这样写:

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

这时IDE会提醒我们关联出InterruptedException,这是一个很古老且基础的异常类。

InterruptedException

当线程正在等待、休眠或以其他方式被占用,并且线程在活动之前或活动期间被中断时抛出。 有时,方法可能希望测试当前线程是否被中断,如果是,则立即抛出此异常。 

                                                                                                                        from JDK1.0

 通过阅读源码注释,我们可以得知,这个InterruptedException异常是与线程相关的异常,且有一个很重要的点,就是活动线程的执行中断行为,可以抛出这样的异常。

简而言之:线程非执行结束中断的,JDK都会提醒你 interrupt

为什么需要InterruptedException?

        正常来说,程序的执行理论上都会是一个有限的资源占用,即程序是可以在有限时间内被执行结束的,可能是不到1ms,甚至长达几小时(规范而言不建议单程序片运行这么久,会建议拆分这个方法)。

        与正常相对的,就是不正常的执行情况:无限的资源占用

        说到无限的资源占用,最经典的就是死循环。在loop中,如果没有中断条件,那么循环内的所有程序看作一个程序片段,那么这个程序片段将无限地占用CPU的时间片资源。

 当然,我们很清楚的知道,非并发编程的情况下,当前执行loop的线程就是我们的主线程。有关多线程与并发的知识参见作者这篇文章:Java设计思想深究----多线程与并发(图文)_kevinmeanscool的博客-CSDN博客一切的缘起是昂贵的CPU我们都十分清楚,计算机的核心是计算,而负责这个功能的组件就是CPU。CPU有一个特性,在一个时刻只能处理一个程序。开发人员编写代码,代码被编译为机器语言,CPU收到机器语言(指令集),开始处理程序,而这个正在被CPU处理的程序就是进程(正在进行的程序)。当CPU正在处理一个程序时,由于其特性,其他程序就只能等待。你可能会想,一个接一个处理,不是很合理的设计吗?这仅仅对于CPU执行指令而言,的确如此。可是,数据在存储媒介上的I/O速度与CPU的速度相比,...https://blog.csdn.net/kevinmeanscool/article/details/122333614?spm=1001.2014.3001.5501

基于此,主线程将永远处于激活状态,由于没有中断条件,将无限地尝试去获取CPU的时间片。

这时,读者可能会觉得,这一定是异常的情况。

对于单机服务的情况,的确这是一种灾难,主线程一直被错误逻辑异常激活,需要我们强制kill掉这个线程。

但对于代理服务(即互联网模式),伺机服务,实际上就是一种无限地资源占用:服务端一旦启用后,将会一直等待请求,这个等待请求的过程实际上就是对于请求集合的轮询行为。

在合理需求的情况下,无限地占用资源也是一种选择方案

因此,在伺机服务的情况下,InterruptedException便是优雅的退出无限占用资源的方式。

举个例子,对于线程,我们实际上是有一个执行时间(T)预期的,超过T的线程,不是程序逻辑有误就是性能恶化的现象,这时,我们可以对这种不太可控的情况进行一个异常捕获:

static class TestThread implements Callable {

        @Override
        public Object call() throws Exception {
            //构造一个1秒以上的线程执行时间
            Thread.sleep(5000);
            System.out.println(Thread.currentThread().getName()+"执行成功");
            return null;
        }
    }
    public static void main(String[] args) {
        //用于轮询的FutureList,建议使用有序线性结构
        List<FutureTask> futureList = new ArrayList<>();
        //new 线程
        for (int i = 0; i < 3; i++){
            // new 一个待唤起的任务
            TestThread testThread = new TestThread();
            // new 一个存放结果的实例
            FutureTask futureTask = new FutureTask(testThread);
            // 填充至轮询列表
            futureList.add(futureTask);
            // 申请一个线程,并指定run()方法是唤起一个任务
            Thread thread = new Thread(futureTask);
            // 激活线程
            thread.start();
        }

        //轮询结果队列
        futureList.forEach(futureTask -> {
            try {
                //监听线程最多执行1秒
                futureTask.get(1, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            } catch (TimeoutException e) {
                // 超时了,主动中断线程
                futureTask.cancel(true);
                //log,这里使用控制台演示
                System.out.println(futureTask+"超时被中断,info:"+e.getMessage());
            }
        });
    }

如此便优雅地实现了线程的退出。

InterruptedException的应用

实际上在使用线程时,编程规范就需要我们去设置线程超时机制,否则资源将无限制地浪费。

线程使用的最为频繁的,就是各种池,如:

  • Web容器:每个请求都将申请一个线程来处理请求
  • 消息队列:每个发布者、订阅者都将申请单独的线程处理事务
  • DB连接池:每个事务请求都将申请一个线程来处理事务
  • 等等

很多类似的生产者-消费者模式,都由JDK原生线程池实现,线程池实现原理可参考博文:

Java设计思想深究----多线程与并发(图文)_kevinmeanscool的博客-CSDN博客一切的缘起是昂贵的CPU我们都十分清楚,计算机的核心是计算,而负责这个功能的组件就是CPU。CPU有一个特性,在一个时刻只能处理一个程序。开发人员编写代码,代码被编译为机器语言,CPU收到机器语言(指令集),开始处理程序,而这个正在被CPU处理的程序就是进程(正在进行的程序)。当CPU正在处理一个程序时,由于其特性,其他程序就只能等待。你可能会想,一个接一个处理,不是很合理的设计吗?这仅仅对于CPU执行指令而言,的确如此。可是,数据在存储媒介上的I/O速度与CPU的速度相比,...https://blog.csdn.net/kevinmeanscool/article/details/122333614?spm=1001.2014.3001.5501原生线程池并没有超时中断机制,这些中间件会在此基础上增加这一机制。

就比如文章开头提到的异常:

nested exception is java.sql.SQLException: interrupt

这就是一类中间件主动中断的线程的行为。但要分清,中断的是事务请求线程并不是连接池的核心线程。

结论 

当然,这是有一个前提,就是将事务请求委托给了数据源中间件,比如Druid等。

如果是我们手动声明-开启事务-预加载-执行,如果连接池满载,只会让事务线程处于阻塞状态,也就是“卡住了”,直到分到资源来处理。

如果我们使用了数据源中间件,在多线程的情况下,很有可能出现连接池满载的情况,比如:

# 最大连接数量
maxActive: 10
# 配置获取连接等待超时的时间
maxWait: 60000

可以通过扩大连接上限和增加等待超时的时间来缓解interrupt出现的可能性、频率。

  • 5
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值