一、情景复现

昨天,公司一个共事,急急忙忙的跑过来找我,说他的我的项目,呈现了一个十分诡异的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 zeroat 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)应用线程池时,须要小心谨慎,做好程序的异样解决,日志记录;