看到一篇不错的文章,让我有思路也写写关于线程池,本文主要关注线程池里的任务跑了异常怎么办
一 Thread能捕获异常吗
public class ThreadExceptionTest { public static class InitialtiveThread implements Runnable { @Override public void run() { System.out.println(3/2); System.out.println(3/0); System.out.println(3/1); } } public static void main(String[] args) { InitialtiveThread runner = new InitialtiveThread(); try { Thread thread = new Thread(runner); thread.start(); } catch(Exception e) { } } }
执行之后发现,如果run方法里抛出异常,通过try,catch 线程的start方法是没法捕获异常的,那是因为该异常是一个UncaughtException,没法通过用户的代码捕获的。
处理方法有两种
1 run方法里面捕获
2 thread.setUncaughtExceptionHandler((Thread t, Throwable e) -> {System.out.println(t.getName() + ": " + e.getMessage());});
方法2就是给线程指定对于UncaughtException的处理器
上述的知识作为前提,为了更好地理解下面的内容
二 线程池执行的情况
首先要区分execute和submit两种方式,这两种方式如果没看过源码,可能还真以为差不多,其实差多了
先说execute,看代码段
int c = ctl.get(); if (workerCountOf(c) < corePoolSize) { if (addWorker(command, true)) return; c = ctl.get(); } if (isRunning(c) && workQueue.offer(command)) { int recheck = ctl.get(); if (! isRunning(recheck) && remove(command)) reject(command); else if (workerCountOf(recheck) == 0) addWorker(null, false);
对于execute方式,用户提交的Runnable没有经过任何的转换,保留原样
现在看Worker的runWorker
try { while (task != null || (task = getTask()) != null) { w.lock(); // If pool is stopping, ensure thread is interrupted; // if not, ensure thread is not interrupted. This // requires a recheck in second case to deal with // shutdownNow race while clearing interrupt 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 { afterExecute(task, thrown); } } finally { task = null; w.completedTasks++; w.unlock(); } } completedAbruptly = false; } finally { processWorkerExit(w, completedAbruptly); }
主动throw了异常,经过第一节的知识,一定会抛出来UncaughtException
我们先看Finally分支
private void processWorkerExit(Worker w, boolean completedAbruptly) {//抛异常的情况completedAbruptly是false的 if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted decrementWorkerCount(); final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { completedTaskCount += w.completedTasks; workers.remove(w);//移除该worker } finally { mainLock.unlock(); } tryTerminate();//检查是否要关闭 int c = ctl.get(); if (runStateLessThan(c, STOP)) { if (!completedAbruptly) { int min = allowCoreThreadTimeOut ? 0 : corePoolSize; if (min == 0 && ! workQueue.isEmpty()) min = 1; if (workerCountOf(c) >= min) return; // replacement not needed } addWorker(null, false);//如果小于核心线程 再次添加 } }
对于execute方式,如果抛出异常,就把该线程移除,如果线程池当前的worker数量不到coreSize,就再添加一个
那么,如果增加了 setUncaughtExceptionHandler 会怎么样呢?
通过跟代码,run会抛出异常,所以还是会走线程移除流程,只不过之前是把异常抛出来,现在是handler里面的处理,但是线程还是会终止的。
再来看submit
submit就好玩了,这里也不详细的跟代码了,简单点说,submit会把传进来的Runnable或者Callable包成FutureTask,这个FutureTask本身还实现了Runnable接口
所以我们看看他的run
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); } } finally { // runner must be non-null until state is settled to // prevent concurrent calls to run() runner = null; // state must be re-read after nulling runner to prevent // leaked interrupts int s = state; if (s >= INTERRUPTING) handlePossibleCancellationInterrupt(s); } }
看得出来,这个run里面是有try catch包装的,这样就不会有异常了。
不过使用submit还是要注意,用了submit后即使是有 setUncaughtExceptionHandler 也是没用的,因为里面catch了。所以submit方式会吞异常,使用submit方式必须手动执行get(),
看看是否有异常