FixedThreadPool吞掉了异常
为了方便遍描述问题,如下是简化后的
public class RunException {
public static void main(String[] args) {
ExecutorService readerPool = Executors.newFixedThreadPool(3);
readerPool.submit(new Runnable() {
public void run() {
throw new RuntimeException("异常");
}
});
readerPool.shutdown();
}
}
此处FixedThreadPool吞掉了异常。
问题
- 为什么不能抛出到外部线程捕获
- submit为什么不能打印报错信息
- execute怎么使用logger打印报错信息
为什么不能抛出到外部线程捕获
jvm会在线程即将死掉的时候捕获所有未捕获的异常进行处理。默认使用的是Thread.defaultUncaughtExceptionHandler
submit为什么不能打印报错信息
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);//创建FutureTask类
execute(ftask);
return ftask;
}
查看FutureTask.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
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);
}
}
接着查看setException(ex);
,将线程状态由completing改为exceptional,并将异常信息存在outcome
中:
//这个方法就是这事线程状态为completing -> exceptional
//同时用outcome保存异常信息。
protected void setException(Throwable t) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = t;
UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
finishCompletion();
}
}
继续查看outcome的使用:
//report会抛出exception信息
private V report(int s) throws ExecutionException {
Object x = outcome;
if (s == NORMAL)
return (V)x;
if (s >= CANCELLED)
throw new CancellationException();
throw new ExecutionException((Throwable)x);
}
//get会调用report()方法
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
- report会抛出exception信息,但report是私有方法;
- get会调用report()方法
所以如果需要获取异常信息就需要调用get()
方法。
execute怎么输入logger日志
查看execute的实现ThreadPoolExecutor.execute()
:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
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);
}
else if (!addWorker(command, false))
reject(command);
}
从代码可知,线程池将任务加入了任务队列,需要看看线程在哪执行任务的。那么只需要看看有没有获取任务的函数,ThreadPoolExecutor.getTask()
即是获取任务的函数,通过查找,ThreadPoolExecutor.runWorker
调用了ThreadPoolExecutor.getTask()
,它应该是执行任务的代码:
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
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) {
//这里直接抛出所有Runtime异常
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);
}
}
代码注释中看到获取RuntimeException
的位置了。
这里抛出的异常在哪里处理呢? 接下来处理是交由jvm处理,从已经学习的知识中只知道jvm调用Thread.dispatchUncaughtException
来处理所有未捕获的异常
/**
* Dispatch an uncaught exception to the handler. This method is
* intended to be called only by the JVM.
*/
private void dispatchUncaughtException(Throwable e) {
getUncaughtExceptionHandler().uncaughtException(this, e);
}
这里可以根据该方法注释解释,意思就是这个方法只用于JVM调用,处理线程未捕获的异常。 继续查看getUncaughtExceptionHandler()
方法:
public interface UncaughtExceptionHandler {s
void uncaughtException(Thread t, Throwable e);
}
// 处理类
private volatile UncaughtExceptionHandler uncaughtExceptionHandler;
// 默认处理类
private static volatile UncaughtExceptionHandler defaultUncaughtExceptionHandler;
/**
* 设置默认的处理类,注意是静态方法,作用域为所有线程设置默认的处理类
**/
public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(
new RuntimePermission("setDefaultUncaughtExceptionHandler")
);
}
defaultUncaughtExceptionHandler = eh;
}
//获取默认处理类
public static UncaughtExceptionHandler getDefaultUncaughtExceptionHandler(){
return defaultUncaughtExceptionHandler;
}
//获取处理类,注意不是静态方法,只作用域该线程
//处理类为空使用ThreadGroup
public UncaughtExceptionHandler getUncaughtExceptionHandler() {
return uncaughtExceptionHandler != null ?
uncaughtExceptionHandler : group;
}
//设置处理类
public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
checkAccess();
uncaughtExceptionHandler = eh;
}
/**
* Dispatch an uncaught exception to the handler. This method is
* intended to be called only by the JVM.
*/
private void dispatchUncaughtException(Throwable e) {
//获取处理类型进行异常处理
getUncaughtExceptionHandler().uncaughtException(this, e);
}
如果线程UncaughtExceptionHandler
处理器为空则threadGroup
处理器 查看threadGroup
:
public void uncaughtException(Thread t, Throwable e) {
if (parent != null) {
parent.uncaughtException(t, e);
} else {
Thread.UncaughtExceptionHandler ueh =
Thread.getDefaultUncaughtExceptionHandler();
if (ueh != null) {
ueh.uncaughtException(t, e);
} else if (!(e instanceof ThreadDeath)) {
System.err.print("Exception in thread \""
+ t.getName() + "\" ");
e.printStackTrace(System.err);
}
}
}
从代码中可以看出,
- 如果父进程不为空,则使用父进程处理未捕获异常;
- 如果无父进程,则获取默认的
UncaughtExceptionHandler
进行处理。- 默认的
UncaughtExceptionHandler
为null,则使用Sytem.err
将错误信息输出; - 默认的
UncaughtExceptionHandler
不为null,则使用UncaughtExceptionHandler
进行处理。
- 默认的
所以有两个方法实现用logger输出:
- Thread定义
uncaughtExceptionHandler
:Thread.setUncaughtExceptionHandler()
,该方法仅能设置某个线程的默认UncaughtExceptionHandler
。 - Thread定义
defaultUncaughtExceptionHandler
:使用Thread.setDefaultUncaughtExceptionHandler
,该方法设置所有线程的默认UncaughtExceptionHandler
。
测试程序
仅某个线程设置默认UncaughtExceptionHandler
public static void oneThreadUncaughtExceptionHandler() {
Thread t1 = new Thread(() -> {
throw new RuntimeException(" t1 runtime exception");
}, "t1");
t1.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println(Thread.currentThread() + "trigger uncaugh exception handler");
}
});
t1.start();
Thread t2 = new Thread(() -> {
throw new RuntimeException(" t2 runtime exception");
}, "t2");
t2.start();
}
设置defaultUncaughtExceptionHandler
public static void defaultThreadUncaughtExceptionHandler() {
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println(Thread.currentThread() + "trigger uncaugh exception handler");
}
});
new Thread(() -> {
throw new RuntimeException(" t1 runtime exception");
}, "t1").start();
new Thread(() -> {
throw new RuntimeException(" t2 runtime exception");
}, "t2").start();
}
解惑
那为什么我们的例子代码中,异常不会输出呢?应该有兜底的
System.err
来输出异常才对。 不是这样的,我们的例子中的异常实际上是处理了的,它捕获了异常,并且保存到了outcome中。仅仅有未捕获的异常,JVM才会调用Thread.dispatchUncaughtException
来处理。