乐趣区

关于java:ThreadPoolExecutor-线程池异常消失之刨根问底

一、情景复现

昨天,公司一个共事,急急忙忙的跑过来找我,说他的我的项目,呈现了一个十分诡异的 BUG,不晓得什么状况?

共事 :我用五个线程计算学生各个科目的问题,最初汇总,本地都是失常的,然而一到测试环境就少了一科问题,也没抛出异样,什么鬼?
油七 :工作线程怎么做的?线程异样解决了吗?为啥不打印日志呢?灵魂三连击,哈哈哈(开玩笑的,这不是我的处事格调)
油七 :行,咱们先看一下代码 …,一顿扫描占卜之后,大抵晓得啥状况了。
共事 :哥,我这程序还有救吗,客户下了死命令,明天解决啊。
油七 :没事,小伙子,不要慌,你先把线程池这里 submit 提交改成 execute 试一下
五分钟之后 …
共事 :卧槽,抛出异样了,我这里计算逻辑有问题,666,这是啥起因啊,为啥我 submit 提交,异样不抛出来啊?
油七 :嗯,这个问题 …
原文解析

二、程序模仿

因为共事的代码逻辑比拟绕,不便于咱们复现问题,因而我写了一个简略的问题实例,作为本篇文章剖析的根据。程序计算用除法代替,除数取到了 0,按情理应该抛出 ArithmeticException。

模仿代码

代码如下:

import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ExceptionMissMain {

    public static class Task implements Runnable {
        String name;
        int a, b;

        public Task(String name, int a, int b) {
            this.name = name;
            this.a = a;
            this.b = b;
        }

        @Override
        public void run() {
            double c = a / b;
            System.out.println("科目:" + name + ", 问题:" + c);
        }

    }

    public static void main(String[] args) throws InterruptedException {ThreadPoolExecutor es = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.MILLISECONDS, new SynchronousQueue<>());
        for (int i = 0; i < 5; i++) {es.submit(new Task(String.valueOf(i), 100, i));
            //es.execute(new Task(String.valueOf(i), 100, i));
            Thread.sleep(2000);
        }
    }
}

后果输入

submit 形式

科目:1, 问题:100.0
科目:2, 问题:50.0
科目:3, 问题:33.0
科目:4, 问题:25.0

短少一科问题,程序运行无异样抛出

execute 形式

Exception in thread "pool-1-thread-1" java.lang.ArithmeticException: / by zero
at com.tiny.juc.boot.pool.ExceptionMissMain$Task.run(ExceptionMissMain.java:30)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:830)

科目:1, 问题:100.0
科目:2, 问题:50.0
科目:3, 问题:33.0
科目:4, 问题:25.0

短少一科问题,程序运行异样抛出

三、刨根问底

看到下面两种形式提交工作,输入后果的不同,submit 形式异样没有了,execute 形式抛出了异样,很多人必定都呈现了疑难?

别纠结了,间接入手刨坟吧,看一看源代码中两个形式到底是如何实现的,不就水落石出了吗?just do it!咱们采纳断点调试的形式,一步一步查看程序运行的过程。

源码追击

execute 实现

1. 首先咱们来看一下 execute 办法的实现,发现程序失常会进入 addWorker 办法

2. 咱们来看一下 addWorker 办法,做了哪些事件?察看上面的代码,咱们会发现,addWorker 办法先创立了一个 Worker 对象,并且将传入的 Runnable 类型的 task 传入到新建的 Worker 中,而后再从 Worker 对象中拿出 thread 变量,再调用了以后 Worker 的 thread 的 start 办法。疑难:start()办法运行的是什么代码?,Worker 对象创立都干了什么事件?Worker 对象的 thread 是怎么创立的?

3. 带着第二步的疑难,咱们再来一次,这次进到 new Worker 外面,看一下。咱们会发现,Worker 对象新建的时候,将本人作为指标对象创立了一个线程,并且赋给了 Worker 中的 thread,咱们看到 Worker 类实现了 Runnable 接口,所以也就是说上一步外面 t.start() 办法,调用的就是指标对象 Worker 本人的 run 办法。

4. 为了验证第三步的解释,咱们在 Thread 类中 run 办法与 Worker 类中的 run 办法,别离打上断点,再运行。发现,的确和咱们料想的一样,程序先进入了 Thread 类中 run 办法,后调用了 Worker 类中的 run 办法,继而调用了 Worker 类中 runWorker 办法。

5. 那么当初,咱们再看一下 runWorker 干了什么事件?咱们发现 runWorker 获取了 Worker 对象的 Runnable task(也就是咱们创立的工作),并且调用了咱们工作的 run 办法。

6.OK,咱们当初只须要看一下,runWorker task.run()办法调用这里的异样解决,就明确了。咱们发现,此处运行有异样捕捉,try catch 了 Throwable 异样,且向上抛出了,而咱们的程序除数取到 0 的异样 ArithmeticException,也包含在其中。

正文:看到这咱们就明确了,后面的程序为什么 execute 办法会抛出异样了吧,行吧,都散了吧。什么,我才刚看爽,你就叫我走?还有 submit 呢,为啥不抛异样啊,什么状况还没说呢,别想溜。。。好吧,咱们持续看下 submit 的底层实现。

submit 实现

1. 首先咱们来看一下 submit 办法的实现,发现程序会将咱们提交的工作通过 newTaskFor 办法转换成 FutureTask
2. 工作转换成 FutureTask 后会调用与后面一样的 execute 办法

3. 看到这咱们就晓得了,也就是说前面还是反复着后面 execute 执行雷同的逻辑,只不过参数变成了 FutureTask,那么最初在 runWorker 办法外面 task.run() 那里,会走 FutureTask 类的 run 办法,去调用咱们定义的工作。
4. 所以咱们去 FutureTask 类中,看一下 run 办法的实现。咱们发现 run 办法中 try catch 了异样,并且调用了 setException 办法,然而在 setException 办法中,将异样赋给了 outcome,未见其余解决。

5. 最初咱们看一下 FutureTask 整个类中 outcome 呈现的中央,发现在 get 办法中通过调用 report 办法返回了 outcome。

6. 所以咱们在程序那里,通过 get 办法去接管,看一下呈现什么后果?后果同 execute 办法一样呈现了异样。

Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero
    at java.base/java.util.concurrent.FutureTask.report(FutureTask.java:122)
    at java.base/java.util.concurrent.FutureTask.get(FutureTask.java:191)
    at com.tiny.juc.boot.pool.ExceptionMissMain.main(ExceptionMissMain.java:39)

正文:看到这咱们终于明确,submit 与 execute 办法实现上的差别了,以及前文的程序代码为什么 submit 提交不抛出异样,而 execute 提交抛出异样了吧。

四、总结

1)submit 办法,针对异样信息捕捉后调用 setException 输入到 FutureTask 中的 outcome;
2)工作如果是用 submit 办法提交的,那就用 futureTask 的 get 办法去接管;
3)execute 办法会将工作的异样信息,向上抛出;
4)应用线程池时,须要小心谨慎,做好程序的异样解决,日志记录;

退出移动版