线程池中线程抛了异常,该如何处理?( 二 )

打印结果如下:
 

线程池中线程抛了异常,该如何处理?

文章插图
 
根据打印结果我们看到,execute方法被线程工厂factory中设置的 UncaughtExceptionHandler?捕捉到异常,而submit方法却没有任何反应!说明UncaughtExceptionHandler在submit中并没有被调用 。这是为什么呢?
在日常使用中,我们知道,execute和submit最大的区别就是execute没有返回值,submit有返回值 。submit返回的是一个future ,可以通过这个future取到线程执行的结果或者异常信息 。
 Future<?> submit = executorService.submit(new task());//打印异常结果System.out.println(submit.get());
线程池中线程抛了异常,该如何处理?

文章插图
从结果看出:submit并不是丢失了异常,使用future.get()?还是有异常打印的!!那为什么线程工厂factory 的UncaughtExceptionHandler?没有打印异常呢?猜测是submit方法内部已经捕获了异常,只是没有打印出来,也因为异常已经被捕获,因此jvm也就不会去调用Thread的UncaughtExceptionHandler去处理异常 。
接下来,验证猜想:
首先看一下submit和execute的源码:
execute方法的源码在这博客中写的很详细,点击查看execute源码,在此就不再啰嗦了
https://blog.csdn.NET/qq_45076180/article/details/108316340
submit源码在底层还是调用的execute方法,只不过多一层Future封装,并返回了这个Future,这也解释了为什么submit会有返回值
 //submit()方法 public <T> Future<T> submit(Callable<T> task) {if (task == null) throw new NullPointerException();//execute内部执行这个对象内部的逻辑,然后将结果或者异常 set到这个ftask里面RunnableFuture<T> ftask = newTaskFor(task);// 执行execute方法execute(ftask);//返回这个ftaskreturn ftask; }【线程池中线程抛了异常,该如何处理?】可以看到submit也是调用的execute,在execute方法中,我们的任务被提交到了addWorker(command, true) ,然后为每一个任务创建一个Worker去处理这个线程,这个Worker也是一个线程,执行任务时调用的就是Worker的run方法!run方法内部又调用了runworker方法!如下所示:
 public void run() {runWorker(this); }final void runWorker(Worker w) {Thread wt = Thread.currentThread();Runnable task = w.firstTask;w.firstTask = null;w.unlock(); // allow interruptsboolean completedAbruptly = true;try {//这里就是线程可以重用的原因,循环+条件判断,不断从队列中取任务//还有一个问题就是非核心线程的超时删除是怎么解决的//主要就是getTask方法()见下文③while (task != null || (task = getTask()) != null) {w.lock();if ((runStateAtLeast(ctl.get(), STOP) ||(Thread.interrupted() &&runStateAtLeast(ctl.get(), STOP))) &&!wt.isInterrupted())wt.interrupt();try {beforeExecute(wt, task);Throwable thrown = null;try {//执行线程task.run();//异常处理} catch (RuntimeException x) {thrown = x; throw x;} catch (Error x) {thrown = x; throw x;} catch (Throwable x) {thrown = x; throw new Error(x);} finally {//execute的方式可以重写此方法处理异常afterExecute(task, thrown);}} finally {task = null;w.completedTasks++;w.unlock();}}//出现异常时completedAbruptly不会被修改为falsecompletedAbruptly = false;} finally {//如果如果completedAbruptly值为true,则出现异常,则添加新的Worker处理后边的线程processWorkerExit(w, completedAbruptly);} }核心就在 task.run(); 这个方法里面了,期间如果发生异常会被抛出 。
  • 如果用execute提交的任务,会被封装成了一个runable任务,然后进去 再被封装成一个worker,最后在worker的run方法里面调用runWoker方法, runWoker?方法里面执行任务任务,如果任务出现异常,用try-catch?捕获异常往外面抛,我们在最外层使用try-catch?捕获到了 runWoker方法中抛出的异常 。因此我们在execute中看到了我们的任务的异常信息 。
  • 那么为什么submit没有异常信息呢? 因为submit是将任务封装成了一个futureTask? ,然后这个futureTask?被封装成worker,在woker的run方法里面,最终调用的是futureTask?的run方法,猜测里面是直接吞掉了异常,并没有抛出异常,因此在worker的runWorker方法里面无法捕获到异常 。
下面来看一下futureTask?的run方法,果不其然,在try-catch中吞掉了异常,将异常放到了 setException(ex);里面
 public void run() {if (state != NEW ||!UNSAFE.compareAndSwapObject(this, runnerOffset,null, Thread.currentThread()))return;try {Callable<V> c = callable;if (c != null && state == NEW) {V result;boolean ran;try {result = c.call();ran = true;} catch (Throwable ex) {result = null;ran = false;//在此方法中设置了异常信息setException(ex);}if (ran)set(result);}//省略下文。。。。。。setException(ex)`方法如下:将异常对象赋予`outcomeprotected void setException(Throwable t) {if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {//将异常对象赋予outcome,记住这个outcome,outcome = t;UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final statefinishCompletion();}}


推荐阅读