前言
在阅读线程相关知识的时候发现线程吞掉异常的知识,故记录下来。
代码示例
ExecutorService executorService = Executors.newFixedThreadPool(1);
try {
executorService.submit(() -> {
Object obj = null;
System.out.println(obj.toString());
});
} catch (Exception e) {
System.out.println("catch Exception");
e.printStackTrace();
}
try {
executorService.execute(() -> {
Object obj = null;
System.out.println(obj.toString());
});
} catch (Exception e) {
System.out.println("catch Exception");
e.printStackTrace();
}
运行结果:
Exception in thread "pool-1-thread-1" java.lang.NullPointerException
at my.jdk.concurrent.ThreadPoolTest.lambda$uncaughtExceptionTest$2(ThreadPoolTest.java:68)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
catch肯定不会获取到信息,同时submit报错信息也没有打印出来,只打印的execute报错信息
问题
- 为什么不能抛出到外部线程捕获
- submit为什么不能打印报错信息
- execute怎么使用logger打印报错新信
原因
为什么不能抛出到外部线程捕获?
jvm会在线程即将死掉的时候捕获所有未捕获的异常进行处理。
submit为什么不能打印报错信息
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) {
//这里捕获了所有异常调用setException
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);
}
}
接着查看setException实现
//这个方法就是这事线程状态为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);
}
所有知道了如果想获取sumbit的异常信息需要调用get方法,如下:
@Test
public void caughtSumbitException() {
ExecutorService executorService = Executors.newFixedThreadPool(1);
Future future = executorService.submit(() -> {
Object obj = null;
System.out.println(obj.toString());
});
try {
future.get();
} catch (Exception e) {
System.out.println("catch NullPointException");
}
}
输出:
catch NullPointException
execute怎么输入logger日志
查看execute代码实现
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) {
//这里直接抛出所有异常
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);
}
}
这里抛出的异常在哪里处理呢?
接下来处理是交由jvm处理,由于本人对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 {
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);
}
如果线程处理器为空则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);
}
}
}
获取默认处理器进行线程处理
默认处理器为空则在Sytem.err就行错误信息输出
到这里有两个方法可以实现用logger输出
- thread定义uncaughtExceptionHandler
- Thread定义defaultUncaughtExceptionHandler
示例代码:
@Test
public void setUncaughtExceptionHandler() throws InterruptedException {
Thread t1 = new Thread(() -> {
for (;;) {
throw new RuntimeException("soming Exception");
}
}, "t1");
t1.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
t1.start();
new Thread(() -> {
for (;;) {
throw new RuntimeException("soming Exception");
}
}, "t2").start();
Thread.sleep(1000);
}
class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
private Thread.UncaughtExceptionHandler defaultHandler;
public MyUncaughtExceptionHandler() {
this.defaultHandler = Thread.getDefaultUncaughtExceptionHandler();
}
@Override
public void uncaughtException(Thread t, Throwable e) {
logger.info("logger println exception info, threadName:{}", t.getName());
}
}
输出:
Exception in thread "t2" java.lang.RuntimeException: soming Exception
at my.jdk.concurrent.ThreadTest.lambda$setUncaughtExceptionHandler$1(ThreadTest.java:27)
at java.lang.Thread.run(Thread.java:745)
[no tracer] 2018-06-05 19:01:55.230 [t1] INFO my.jdk.concurrent.ThreadTest - logger println exception info, threadName:t1
可以看到setUncaughtExceptionHandler只作用与t1,t2还是system.err输出
@Test
public void defaultUncaughtExceptionHandler() throws InterruptedException {
Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
new Thread(() -> {
for (;;) {
throw new RuntimeException("soming Exception");
}
}, "t1").start();
new Thread(() -> {
for (;;) {
throw new RuntimeException("soming Exception");
}
}, "t2").start();
Thread.sleep(1000);
}
class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
private Thread.UncaughtExceptionHandler defaultHandler;
public MyUncaughtExceptionHandler() {
this.defaultHandler = Thread.getDefaultUncaughtExceptionHandler();
}
@Override
public void uncaughtException(Thread t, Throwable e) {
logger.info("logger println exception info, threadName:{}", t.getName());
}
}
输出:
[no tracer] 2018-06-05 19:03:37.292 [t2] INFO my.jdk.concurrent.ThreadTest - logger println exception info, threadName:t2
[no tracer] 2018-06-05 19:03:37.292 [t1] INFO my.jdk.concurrent.ThreadTest - logger println exception info, threadName:t1
作用域为所有线程